官方解释 Segment
- Segment 是 buffer 的切割部分.
- 每个 buffer 中的 Segment 都是循环链表中的节点,持有上一个 Segment 和下一个 Segment 的引用.
- 每个缓存池中的 Segment 都是单链表中的节点.
- Segment 底层数组可以被 buffer 和字符串共享.当一个 Segment 是被共享状态时不可以被回收,其字节数据也不可以只读不可写.
- 唯一例外的是当 owner 为 true 时,数据区间为 [limit,SIZE] 中可以做写操作.
- 每个数组都有一个 Segment 是持有者.
- Positions, limits, prev, next 不可以被共享.
由于 Segment 的代码不是很多,所以直接贴代码和注释.
// Segment.java
final class Segment {
// 定义了 segment 数据字节数最大值为 8kb
static final int SIZE = 8192;
// SHARE_MINIMUM 是调用 split() 时根据操作字节大小区分使用共享 segment 还是使用数据复制的标准
static final int SHARE_MINIMUM = 1024;
// 数据
final byte[] data;
// 标记下一个有效数据字节的下标
int pos;
// data 可写的下一个字节的下标,也是有效数据的最后一个字节下一个字节的下标
int limit;
// 是否与其他 segment 共享同一个数组 data,如果为 true data 中 [0,limit] 都是只读不可写的
boolean shared;
// 是否 data 的所有者,即与 shared 互斥,可以对 data 进行读写
boolean owner;
// 下一个 segment
Segment next;
// 上一个 segment
Segment prev;
Segment() {
this.data = new byte[SIZE];
this.owner = true;
this.shared = false;
}
Segment(byte[] data, int pos, int limit, boolean shared, boolean owner) {
this.data = data;
this.pos = pos;
this.limit = limit;
this.shared = shared;
this.owner = owner;
}
/**
* 返回一个新的 segment 并与当前 segment 使用同一个 data 引用
*/
Segment sharedCopy() {
// 设置 shared 为 true 可以防止 segment 被回收
shared = true;
return new Segment(data, pos, limit, true, false);
}
/**
* 返回一个新的 segment , data 与当前 segment.data 的克隆
*/
Segment unsharedCopy() {
return new Segment(data.clone(), pos, limit, false, true);
}
/**
* 把当前 segment 从循环链表中移除,如果移除后链表为空,就返回 null
*/
public @Nullable Segment pop() {
// 如果当前 segment 下一个节点就是指向它自己,那么链表只有一个 segment,result 为 null,
// 且下面两行代码的执行毫无意义.
Segment result = next != this ? next : null;
prev.next = next;
next.prev = prev;
next = null;
prev = null;
return result;
}
/**
* 把 segment 添加到循环链表中,变成当前 segment 的上一个节点,并返回被添加的 segment
*/
public Segment push(Segment segment) {
segment.prev = this;
segment.next = next;
next.prev = segment;
next = segment;
return segment;
}
/*
* 把当前 Segment 拆分成两部分,数据范围分别是 [pos, pos+byteCount] [pos+byteCount, limit]
*/
public Segment split(int byteCount) {
// byteCount 不可以 <= 0 || byteCount 必须大于有效数据的大小,不然没必要拆分
if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
Segment prefix;
// 两个性能指标:
// - 避免复制操作.可以使用共享 segment 达到目的.
// - 避免共享数据量小的 segment,这样会在链表中出现一串数据量小的 segment 且他们都是只读,会影响性能.
// 为了得到平衡,只会在复制操作代价足够大的时候才使用共享 segment
// 当 byteCount 大于 SHARE_MINIMUM 的时候使用共享 segment 操作,
// 小于 SHARE_MINIMUM 的时候使用数据复制操作
if (byteCount >= SHARE_MINIMUM) {
prefix = sharedCopy();
} else {
// 从缓存池中获取 segment
prefix = SegmentPool.take();
// 把当前 segment 中区间为 [pos,byteCount] 的数据复制到 prefix.data [0,byteCOunt] 中
System.arraycopy(data, pos, prefix.data, 0, byteCount);
}
// 设置 prefix 的 limit
prefix.limit = prefix.pos + byteCount;
// 当前 segment 的 pos 向后移动 byteCount
pos += byteCount;
// 把 prefix 添加到链表中且为当前 segment 的上一个节点
prev.push(prefix);
return prefix;
}
/**
* 调用该方法,可以在当前节点与它的上一个节点有效数据字节数相加小于 Segemnt.SIZE 的时候
* 合并成一个 segment,然后回收当前 segment.通常是由尾结点 tail 调用该方法.
*/
public void compact() {
if (prev == this) throw new IllegalStateException();
// 当 prev 是只读的时候不可以合并
if (!prev.owner) return;
// 操作字节数就是当前 segment 的数据大小
int byteCount = limit - pos;
// 计算 prev 的可写范围大小
// 如果 prev 是被共享的 segment,它的可写范围是 [limit,SIZE],不是共享的话范围是 [0,pos],[limit,SIZE]
int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
// 如果 prev 可写大小小于当前 segment 数据大小,就不可以合并了
if (byteCount > availableByteCount) return;
// 把当前 segment 数据写进 prev 中
writeTo(prev, byteCount);
// 把当前 segment 从循环链表中移除
pop();
// 回收当前 segment
SegmentPool.recycle(this);
}
// 把当前 segment 数据中的前 byteCount 个字符写进另一个 segment sink 中
public void writeTo(Segment sink, int byteCount) {
// 如果 sink 是只读的抛异常
if (!sink.owner) throw new IllegalArgumentException();
// 如果忽略 sink 数据区间 [0,pos] 的大小,只拿区间 [limit,SIZE] 与 byteCount 作对比的话,
// 大于 byteCount 的话可以直接把数据写进 sink 数据区间 [limit, limit+byteCount] 中
// 小于 byteCount 的话要先把 sink 数据往前移动到 [0,limit-pos] 中,再把数据写进 sink 中
if (sink.limit + byteCount > SIZE) {
// 如果 sink 是被共享的 segment ,可以在 limit 之后即区间[0, limit] 都是只读不可写的,所以抛异常
if (sink.shared) throw new IllegalArgumentException();
// 如果 sink 可写空间大小小于 byteCount ,抛异常
if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException();
// 移动 sink 数据到 [0,limit-pos] 中
System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);
// 移动后调整 sink.limit 和 sink.pos
sink.limit -= sink.pos;
sink.pos = 0;
}
// 把当前 segment 的数据写 byteCount 字节到 sink 中
System.arraycopy(data, pos, sink.data, sink.limit, byteCount);
// 调整 sink.limit 和当前 pos
sink.limit += byteCount;
pos += byteCount;
}
}
总结
- Segment 是作为 buffer 用来切割数据的存在,通过循环链表的方式把分散的数据连在一起组成 buffer 中的数据.
- Segment 中存储数据最大容量是 8kb
- Segment 的 split() 中为了减少调用 System.arraycopy 带来的 CPU 损耗,操作字节数大于 1kb 时用共享 segment 的方式代替复制操作.
- Segment 的 compact() 可以把两个数据占用率小于 50% 且连续的 segment 合并成一个,减少内存消耗.
网友评论