当前位置:首页 > Web开发 > 正文

但在实现时只做了检查

2024-03-31 Web开发

ByteBuf ByteBuf是什么

为了平衡数据传输时CPU与各类IO设备速度的差异性,计算机设计者引入了缓冲区这一重要抽象。jdkNIO库供给了java.nio.Buffer接口,并且供给了7种默认实现,常见的实现类为ByteBuffer。不过netty并没有直接使用nio的ByteBuffer,这主要是由于jdk的Buffer有以下几个错误谬误:

当挪用allocate要领分配内存时,Buffer的长度就固定了,不能动态扩展和收缩,当写入数据大于缓冲区的capacity时会产生数组越界错误

Buffer只有一个位置标识表记标帜位属性position,读写切换时,必需先挪用flip或rewind要领。不只如此,因为flip的切换

Buffer只供给了存取、翻转、释放、标识表记标帜、对照、批量移动等缓冲区的根基操纵,想使用高级的成果(好比池化),就得本身手动进行封装及维护,使用非常未便利。
也因此,netty实现了本身的缓冲区——ByteBuf,连名字都如此相似。那么ByteBuf是如何规避ByteBuffer的错误谬误的?
第一点显然是很好解决的,由于ByteBuf底层也是数组,那么它就可以像ArrayList一样,在写入操纵时进行容量查抄,当容量不敷时进行扩容。
第二点,ByteBuf通过2个索引readerIndex,writerIndex将数组分为3部分,如下图所示

+-------------------+------------------+------------------+ | discardable bytes | readable bytes | writable bytes | | | (CONTENT) | | +-------------------+------------------+------------------+ | | | | 0 <= readerIndex <= writerIndex <= capacity

初始化时,readerIndex和writerIndex都是0,跟着数据的写入writerIndex会增加,此时readable byte部分增加,writable bytes减少。当读取时,discardable bytes增加,readable bytes减少。由于读操纵只改削readerIndex,写操纵只改削writerIndex,让ByteBuf的使用越发容易理解,制止了由于遗漏flip导致的成果异常。
别的,当挪用discardReadBytes要领时,可以把discardable bytes这部分的内存释放。总体想法是通过将readerIndex移动到0,writerIndex移动到writerIndex-readerIndex下标,具体移动下标的方法依据ByteBuf实现类有所差别。这个要领可以显著提高缓冲区的空间复用率,制止无限度的扩容,但会产生字节数组的内存复制,属于以时间换空间的做法。

ByteBuf重要API read、write、set、skipBytes

前3个系列的要领及最后一个skipBytes都属于转变指针的要领。举例来说,readByte会移动readerIndex1个下标位,而int是4个byte的巨细,所以readInt会移动readerIndex4个下标位,相应的,writeByte会移动writerIndex1个下标位,writeInt会移动writerIndex4个下标位。set系列要领对照特殊,它的参数为index和value,意即将value写入指定的index位置,但这个操纵不会转变readerIndex和writerIndex。skipBytes对照简单粗暴,直接将readerIndex移动指定长度。

mark和reset

markReaderIndex和markWriterIndex可以将对应的指针做一个符号,当需要从头操纵这部分数据时,再使用resetReaderIndex或resetWriterIndex,将对应指针复位到mark的位置。

duplicate、slice、copy

这3种要领都可以复制一份字节数组,差别之处在于duplicate和slice两个要领返回的新ByteBuf和原有的老ByteBuf之间的内容会互相影响,而copy则不会。duplicate和slice的区别在于前者复制整个ByteBuf的字节数组,而后者默认仅复制可读部分,但可以通过slice(index, length)支解指定的区间。

retain、release

这是ByteBuf接口担任自ReferenceCounted接口的要领,用于引用计数,以便在不使用东西时及时释放。实现思路是当需要使用一个东西时,计数加1;不再使用时,计数减1。考虑到多线程场景,一般也多给与AtomicInteger实现。netty却另辟蹊径,选择了volatile + AtomicIntegerFieldUpdater这样一种更节省内存的方法。

ByteBuf扩容

在ByteBuf写入数据时会查抄可写入的容量,若容量不敷会进行扩容。

final void ensureWritable0(int minWritableBytes) { if (minWritableBytes <= writableBytes()) { return; } int minNewCapacity = writerIndex + minWritableBytes; int newCapacity = alloc().calculateNewCapacity(minNewCapacity, maxCapacity); int fastCapacity = writerIndex + maxFastWritableBytes(); if (newCapacity > fastCapacity && minNewCapacity <= fastCapacity) { newCapacity = fastCapacity; } capacity(newCapacity); }

忽略一些查验性质的代码后,可以看到扩容时先测验考试将现有写索引加上需要写入的容量巨细作为最小新容量,并挪用ByteBufAllocate的calculateNewCapacity要领进行计算。跟入这个要领:

public int calculateNewCapacity(int minNewCapacity, int maxCapacity) { final int threshold = CALCULATE_THRESHOLD; // 4 MiB page if (minNewCapacity == threshold) { return threshold; } if (minNewCapacity > threshold) { int newCapacity = minNewCapacity / threshold * threshold; if (newCapacity > maxCapacity - threshold) { newCapacity = maxCapacity; } else { newCapacity += threshold; } return newCapacity; } int newCapacity = 64; while (newCapacity < minNewCapacity) { newCapacity <<= 1; } return Math.min(newCapacity, maxCapacity); }

温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/web/32130.html