netty4源码阅读与分析---ByteBuf

在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不同的是它维护了读写索引,读写不需要进行切换,大大提高了性能。



猜你喜欢

转载自blog.csdn.net/chengzhang1989/article/details/80388963
今日推荐