在worker线程处理read事件时,会将读取到的内容写入到ByteBuf中,供后续的操作,这里我们来研究下其内部结构。
ByteBuf是一个抽象类,AbstractByteBuf这个抽象类基本实现了ByteBuf,下面阅读AbstractByteBuf的实现来理解ByteBuf.在AbstractByteBuf里面定义了下面5个变量:
int readerIndex;//读索引
int writerIndex;//写索引
private int markedReaderIndex;//标记读索引
private int markedWriterIndex;//标记写索引
private int maxCapacity;//最大容量
最大容量默认为Integer.MAX_VALUE,与jdk的ByteBuffer相比,byteBuf维护了读和写的索引,不存在ByteBuffer的flip读写切换了。我们看下read和write方法,以字节为例:
public ByteBuf writeByte(int value) {
ensureWritable0(1);
_setByte(writerIndex++, value);
return this;
}
final void ensureWritable0(int minWritableBytes) {
ensureAccessible();
if (minWritableBytes <= writableBytes()) {//capacity() - writerIndex
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);//扩容或者收缩
}
public byte readByte() {
checkReadableBytes0(1);
int i = readerIndex;
byte b = _getByte(i);
readerIndex = i + 1;
return b;
}
private void checkReadableBytes0(int minimumReadableBytes) {
ensureAccessible();
if (readerIndex > writerIndex - minimumReadableBytes) {
throw new IndexOutOfBoundsException(String.format(
"readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
readerIndex, minimumReadableBytes, writerIndex, this));
}
}
在write的时候如果发现空间不够,进行扩容,扩容是按照2的次方进行扩容,读写的时候都会校验当前buffer是否可读/写。在ByteBuf的源码中,我们可以看到readerIndex和writerIndex一定满足如下图的条件:
discardable bytes是可以回收的字节,是我们已经读取过的字节,通过调用ByteBuf.discardReadBytes()来回收已经读取过的字节,discardReadBytes()将回收从索引0到readerIndex之间的字节,此时读写索引会变成如下图的格式:
下面我们来看下可读与可写的条件:
public boolean isWritable() {
return capacity() > writerIndex;
}
public boolean isReadable() {
return writerIndex > readerIndex;
}
下面我们来看下创建ByteBuf的方法:
CompositeByteBuf compBuf = Unpooled.compositeBuffer();
ByteBuf heapBuf = Unpooled.buffer(4);
ByteBuf directBuf = Unpooled.directBuffer(4);
其中heapBuf是分配在jvm堆上的,其优点是数据存储在JVM的堆中可以快速创建和快速释放,并且可以直接访问数组,缺点是写数据都要先将数据拷贝到directBuffer再进行传递。directBuf是分配在jvm堆外的,优点是无需从JVM拷贝数据到直接缓冲区,性能好,缺点是分配和释放内存较heapBuf复杂,不支持的直接的数组访问,其访问例子如下:
ByteBuf directBuf = Unpooled.directBuffer(4);
if(!directBuf.hasArray()){
int length = directBuf.readableBytes();
byte[] arr = new byte[length];
directBuf.getBytes(0, arr);
}
所以通常情况下,heapBuf使用起来比较方便。如果对性能有较高的要求,比如大量I/O读写,这种情况下可以考虑使用。compBuf是netty特有的复合型buffer,类似于一个列表,我们可以动态的往里面添加和删除其中的ByteBuf,其访问方式与directBuf类似。
总结一下,ByteBuff内部的数据结构本质上是一个byte数组,与jdk原生的ByteBuffer不同的是它维护了读写索引,读写不需要进行切换,大大提高了性能。