当我们进行数据传输的时候,往往需要使用到缓冲区,常用的缓冲区是 JDK NIO 类库提供的 java.nio.Buffer。
ByteBuffer的缺点:
1. ByteBuffer 长度固定,一旦分配完成,它的容量不能动态扩展和收缩,当需要编码的 POJO 对象大于 ByteBuffer 的容量时,会发生索引越界异常;
2. ByteBuffer 只有一个标识位置的指针 position,读写的时候需要手工调用 flip() 和 rewind() 等,使用者必须小心谨慎地处理这些 API,否则很容易导致程序处理失败;
3. ByteBuffer 的 API 功能有限,一些高级和实用的特性它不支持,需要使用者自己编程实现。
1、ByteBuf 功能说明
1.JDK ByteBuffer 由于只有一个位置指针用于处理读写操作, 因此每次读写的时候都需要额外调用 flip() 和 clear() 等方法否则功能将出错。
ByteBuffer buffer = ByteBuffer.allocate(88);
String value = "Netty 权威指南";
buffer.put(value.getBytes());
buffer.flip();
byte[] vArray = new byte[buffer.remaining()];
buffer.get(vArray);
String decodeValue = new String(vArray)
下面看一下调用 flip() 操作前后的对比。
如果不做 flip 操作,读取到的将是position 到 capacity 之间的错误内容。
当执行 flip() 操作之后,它的 limit 被设置为 position, position 设置为 0,capacity不变。由于读取的内容是从position 到 limit 之间因此它能够正确地读取到之前写入缓冲区的内容。
2、ByteBuf 通过两个位置指针来协助缓冲区的读写操作,读操作使用 readerIndex,写操作使用 writerIndex。
readerIndex 和 writerIndex 的取值一开始都是0,随着数据的写入 writerIndex 会增加,读取数据会使 readerIndex 增加,但是它不会超过 writerIndex。在读取之后,0 ~ readerIndex 被视为 discard (已读)的,调用 discardReadBytes 方法,可以释放这部分空间,它的作用类似于 ByteBuffer 的compact方法。ReaderIndex 和 writerIndex 之间的数据是可读取的,等价于 ByteBuffer position 和 limit 之间的数据。writerIndex 和capacity 之间的空间是可写的,等价于 ByteBuffer limit 和 capacity 之间的可用空间。
由于写操作不修改 readerIndex指针,读操作不修改 writerIndex 指针,因此读写之间不再需要调整指针位置,这极大地简化了缓冲区的读写操作,避免了由于遗漏或者不熟悉 flip 操作导致的功能异常。
3、ByteBuf 如何实现动态扩展?
通常情况下,ByteBuffer 进行put 操作的时候,如果缓冲区剩余可写空间不够,就会发生 BufferOverflowException异常。为了避免发生这个问题,通常在进行 put 操作的时候会对剩余可用空间进行校验。如果剩余空间不足,需要重新创建一个 新的 ByteBuffer, 并将之前的 ByteBuffer 复制到新创建的 ByteBuffer 中,最后释放老的ByteBuffer。
ByteBuf 对 write 操作进行了封装,由 ByteBuf 的 write 操作负责进行剩余可用空间的校验,如果可用缓冲区不足,ByteBuf 会自动进行扩展。通过源码分析,我们发现当进行 write 操作时,会对需要 write 的字节进行校验。如果可写的字节数小于 需要写入的字节数,并且需要写入的字节数小于可写的最大字节数,就对缓冲区进行动态扩展。
2、ByteBuf 的 API介绍
1. 顺序读操作
ByteBuf 的 read操作类似于 ByteBuffer 的 get 操作,
2. 顺序写操作
ByteBuf 的 write 操作类似于 ByteBuffer 的 put 操作。
3. readerIndex 和 writerIndex
这两个指针变量用于支持顺序读取和写入操作:readerIndex 用于标识读取索引,writerIndex 用于标识写入索引。
4. Discardable bytes
相比于其他的 Java 对象,缓冲区的分配和释放是个耗时的操作,因此,我们需要尽量重用它们。由于缓冲区的动态扩张需要进行字节数组的复制,它是个耗时的操作,因此,为了最大程度地提升性能,往往需要尽最大努力提升缓冲区的重用率。