一、 测试代码:
public static void main(String[] args) {
ByteBuf buffer = Unpooled.buffer(7);
ByteBuf buffer1 = Unpooled.buffer(10);
buffer.writeBytes("version".getBytes());
buffer1.writeBytes("version".getBytes());
ByteBuf byteBuf = Unpooled.wrappedBuffer(buffer, buffer1);
byteBuf.writeBytes("src".getBytes());
byteBuf.writeBytes("src".getBytes());
System.out.println(byteBuf.readerIndex());
System.out.println(byteBuf.writerIndex());
}
从 wrapperBuffer开始进入调试:ByteBuf byteBuf = Unpooled.wrappedBuffer(buffer, buffer1);
这个函数有两个参数,第一个参数是决定最大组件的数量,第二个可变参数buffers的数量。
首先根据buffers的数量进行,这里因为我们是两个buffer,会进入default
public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuf... buffers) {
switch (buffers.length) {
case 0:
break;
case 1:
ByteBuf buffer = buffers[0];
if (buffer.isReadable()) {
return wrappedBuffer(buffer.order(BIG_ENDIAN));
} else {
buffer.release();
}
break;
default:
for (int i = 0; i < buffers.length; i++) {
ByteBuf buf = buffers[i];
if (buf.isReadable()) {
**return new CompositeByteBuf(ALLOC, false, maxNumComponents, buffers, i, buffers.length);**
}
buf.release();
}
break;
}
return EMPTY_BUFFER;
}
首先进入CompositeByteBuf的构造函数:
CompositeByteBuf(
ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteBuf[] buffers, int offset, int len) {
super(AbstractByteBufAllocator.DEFAULT_MAX_CAPACITY);
if (alloc == null) {
throw new NullPointerException("alloc");
}
if (maxNumComponents < 2) {
throw new IllegalArgumentException(
"maxNumComponents: " + maxNumComponents + " (expected: >= 2)");
}
this.alloc = alloc;
this.direct = direct;
this.maxNumComponents = maxNumComponents;
components = newList(maxNumComponents);
**addComponents0(false, 0, buffers, offset, len);**
consolidateIfNeeded();
setIndex(0, capacity());
}
进入addComponents0函数,其中Component是CompositeByteBuf的一个私有内部类,只能在该类内部使用和获取。
一个Component会持有一个buffer。CompositeByteBuf实际上是一个Component的集合,并通过读写来动态的维护ridx和widx。
private static final class Component {
final ByteBuf buf;
// buffer长度
final int length;
// 这个字段标识着该Component索引之前(不包含自身)的所有Component的endOffset偏移量之和
int offset;
// 这个字段标识着该Component索引之前(连同他自身)的所有Component的endOffset偏移量之和,
// 也就是到该buffer为止的所有buffer的readableBytes之和
int endOffset;
Component(ByteBuf buf) {
this.buf = buf;
length = buf.readableBytes();
}
void freeIfNecessary() {
buf.release(); // We should not get a NPE here. If so, it must be a bug.
}
}
接下来进入addComponents方法,这里就是一个while循环,同时进行一些动态校验:
private int addComponents0(boolean increaseWriterIndex, int cIndex, ByteBuf[] buffers, int offset, int len) {
checkNotNull(buffers, "buffers");
int i = offset;
try {
checkComponentIndex(cIndex);
// No need for consolidation
while (i < len) {
// Increment i now to prepare for the next iteration and prevent a duplicate release (addComponent0
// will release if an exception occurs, and we also release in the finally block here).
ByteBuf b = buffers[i++];
if (b == null) {
break;
}
***cIndex = addComponent0(increaseWriterIndex, cIndex, b) + 1;***
int size = components.size();
if (cIndex > size) {
cIndex = size;
}
}
return cIndex;
} finally {
for (; i < len; ++i) {
ByteBuf b = buffers[i];
if (b != null) {
try {
b.release();
} catch (Throwable ignored) {
// ignore
}
}
}
}
}
然后进入最终的核心方法:addComponent0:
private int addComponent0(boolean increaseWriterIndex, int cIndex, ByteBuf buffer) {
assert buffer != null;
boolean wasAdded = false;
try {
checkComponentIndex(cIndex);
int readableBytes = buffer.readableBytes();
// No need to consolidate - just add a component to the list.
@SuppressWarnings("deprecation")
// buffer封装Component
Component c = new Component(buffer.order(ByteOrder.BIG_ENDIAN).slice());
if (cIndex == components.size()) {
wasAdded = components.add(c);
if (cIndex == 0) {
// 如果是第一个组件,就将它的readableBytes赋值给endOffset。
c.endOffset = readableBytes;
} else {
// 维护偏移量
// 获取前一个组件,然后将它的endOffset赋值给当前组件的offset
Component prev = components.get(cIndex - 1);
c.offset = prev.endOffset;
c.endOffset = c.offset + readableBytes;
}
} else {
components.add(cIndex, c);
wasAdded = true;
if (readableBytes != 0) {
updateComponentOffsets(cIndex);
}
}
if (increaseWriterIndex) {
// 这里维护组合buffer的读索引
writerIndex(writerIndex() + buffer.readableBytes());
}
return cIndex;
} finally {
if (!wasAdded) {
buffer.release();
}
}
}
这里我们看到,netty在构建Composite的Component组合时,同时在它的内部维护了两个偏移量。
offset和endOffset。
我们知道,CompositeByteBuf只是一个逻辑上的组合buffer,内部实际上是多个独立的buffer。
因此,组合buffer的readableBytes必然是所有buffer的所有可读字节之和。
因此,每当读完一个buffer之后,就必然要取下个offset与前一个endOffset相同的buffer去读取数据,而不是在当前readableBytes==0的buffer内继续读取。
接下来看一下,读取和写入:
读取:
@Override
public CompositeByteBuf getBytes(int index, byte[] dst) {
return (CompositeByteBuf) super.getBytes(index, dst);
}
@Override
public ByteBuf getBytes(int index, byte[] dst) {
getBytes(index, dst, 0, dst.length);
return this;
}
@Override
public CompositeByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
checkDstIndex(index, length, dstIndex, dst.length);
if (length == 0) {
return this;
}
**// 先把读取的索引位置转换成component内的索引位置
int i = toComponentIndex(index);**
while (length > 0) {
// 拿到index所处的component
Component c = components.get(i);
// 获取component中的buffer
ByteBuf s = c.buf;
// 偏移量
int adjustment = c.offset;
// 获取需要在当前buffer读取的字节数
int localLength = Math.min(length, s.capacity() - (index - adjustment));
// 读取
s.getBytes(index - adjustment, dst, dstIndex, localLength);
// 维护读写索引位置
index += localLength;
dstIndex += localLength;
// 剩余需要读取的长度
length -= localLength;
i ++;
}
return this;
}
/**
* 返回给定偏移量的索引位置
* Return the index for the given offset
*/
public int toComponentIndex(int offset) {
checkIndex(offset);
for (int low = 0, high = components.size(); low <= high;) {
// 查找index所处的位置,即 c.offset <= offset < c.endOffset
int mid = low + high >>> 1;
Component c = components.get(mid);
if (offset >= c.endOffset) {
low = mid + 1;
} else if (offset < c.offset) {
high = mid - 1;
} else {
return mid;
}
}
throw new Error("should not reach here");
}
CompositeByteBuf的读取是比较简单的。
getBytes的时候需要入参读取的开始位置,然后根据这个其实位置来判断将要读取的component的位置,
也就是通过 查找index所处的位置,即 c.offset <= offset < c.endOffset
进而获取component的索引位置,然后通过维护索引位置以及需要读取的长度length,循环的读取component。
接下来看写操作:
我们说CompositeByteBuf是用来把多个buffer组合成一个逻辑buffer。是netty zero-copy的关键。
那么这里netty是如何把需要新的内容写入组合buffer中呢?
实际上,netty是在所有的buffer之外,新增加了一个Component,同事利用这个component进行读写,这样
就能保证不会影响原来的buffer,并且不需要内存复制就完成了向组合缓冲区中写入数据。
源码:
@Override
public CompositeByteBuf writeBytes(byte[] src) {
return (CompositeByteBuf) super.writeBytes(src);
}
@Override
public ByteBuf writeBytes(byte[] src) {
writeBytes(src, 0, src.length);
return this;
}
@Override
public CompositeByteBuf writeBytes(byte[] src, int srcIndex, int length) {
return (CompositeByteBuf) super.writeBytes(src, srcIndex, length);
}
@Override
public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
ensureAccessible();
// 动态扩容
**ensureWritable(length);**
setBytes(writerIndex, src, srcIndex, length);
writerIndex += length;
return this;
}
// 动态扩容:以64byte为初始容量进行倍增,达到4MB以后,以4MB为步幅进行步进
// 这么做的原因是为了在初始容量不大的时候,可以很快的达到所需要的容量,同时在容量小的时候,倍增并不会浪费更多的内存
// 但是容量增加到足够大的时候,再进行倍增就很容易产生内存浪费诶。这里的4MB是一个经验值。
// CompositeByteBuf在这里面进行了重组,它重写了capacity()方法,并且在这里面padding了一个组件
private void ensureWritable0(int minWritableBytes) {
if (minWritableBytes <= writableBytes()) {
return;
}
if (minWritableBytes > maxCapacity - writerIndex) {
throw new IndexOutOfBoundsException(String.format(
"writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
writerIndex, minWritableBytes, maxCapacity, this));
}
// Normalize the current capacity to the power of 2.
**int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);**
// Adjust to the new capacity.
capacity(newCapacity);
@Override
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
if (minNewCapacity < 0) {
throw new IllegalArgumentException("minNewCapacity: " + minNewCapacity + " (expected: 0+)");
}
if (minNewCapacity > maxCapacity) {
throw new IllegalArgumentException(String.format(
"minNewCapacity: %d (expected: not greater than maxCapacity(%d)",
minNewCapacity, maxCapacity));
}
final int threshold = CALCULATE_THRESHOLD; // 4 MiB page
if (minNewCapacity == threshold) {
return threshold;
}
// If over threshold, do not double but just increase by threshold.
if (minNewCapacity > threshold) {
int newCapacity = minNewCapacity / threshold * threshold;
if (newCapacity > maxCapacity - threshold) {
newCapacity = maxCapacity;
} else {
newCapacity += threshold;
}
return newCapacity;
}
// Not over threshold. Double up to 4 MiB, starting from 64.
// 以64byte为基数进行倍增
int newCapacity = 64;
while (newCapacity < minNewCapacity) {
newCapacity <<= 1;
}
return Math.min(newCapacity, maxCapacity);
}
CompositeByteBuf重写了capacity这个计算新容量的方法:
这里netty重新分配了一个buffer,并且将它padding到了Composite的Component中,并且重新维护了CompositeByteBuf的读写索引。
这个新分配的ByteBuf的容量是重新计算的容量减去现有CompositeByteBuf的容量
这里有一个疑问。使用CompositeByteBuf进行写入之后,第三个padding的bytebuf的读写索引会变得很奇怪,有兴趣的同学可以观察一下。
@Override
public CompositeByteBuf capacity(int newCapacity) {
checkNewCapacity(newCapacity);
int oldCapacity = capacity();
if (newCapacity > oldCapacity) {
final int paddingLength = newCapacity - oldCapacity;
ByteBuf padding;
int nComponents = components.size();
if (nComponents < maxNumComponents) {
// 重新获取buffer
padding = allocBuffer(paddingLength);
padding.setIndex(0, paddingLength);
// 新增组件
addComponent0(false, components.size(), padding);
} else {
padding = allocBuffer(paddingLength);
padding.setIndex(0, paddingLength);
// FIXME: No need to create a padding buffer and consolidate.
// Just create a big single buffer and put the current content there.
addComponent0(false, components.size(), padding);
consolidateIfNeeded();
}
} else if (newCapacity < oldCapacity) {
int bytesToTrim = oldCapacity - newCapacity;
for (ListIterator<Component> i = components.listIterator(components.size()); i.hasPrevious();) {
Component c = i.previous();
if (bytesToTrim >= c.length) {
bytesToTrim -= c.length;
i.remove();
continue;
}
// Replace the last component with the trimmed slice.
Component newC = new Component(c.buf.slice(0, c.length - bytesToTrim));
newC.offset = c.offset;
newC.endOffset = newC.offset + newC.length;
i.set(newC);
break;
}
if (readerIndex() > newCapacity) {
setIndex(newCapacity, newCapacity);
} else if (writerIndex() > newCapacity) {
writerIndex(newCapacity);
}
}
return this;
}
总结:
到这里CompositeByteBuf的核心代码基本上就完成了。通过源码可以大致看出,CompositeByteBuf实质上是把多个ByteBuf组合成了一个逻辑上的buffer。
同时各个buffer之间独立维护自己的读写索引。而外层的CompositeByteBuf自己会单独维护一个读写索引。
读取的时候,就是根据入参的读取位置,查找相应的component,然后开始顺序读取各个buffer中的可读字节。
新增的时候,就会根据动态扩容的结果,向现有的compoennt中padding一个新的buffer,利用新的buffer进行读写,从而不影响原有buffer。