Java NIO中的Buffer用于和NIO通道进行交互,数据可以从通道读入缓冲区,也可以从缓冲区写入到通道中。所以说,Buffer其实就是一块可以读写数据的内存,我们将其包装为一个Java对象来提供一系列读写操作。Netty并没有直接使用Java NIO的Buffer实现,而是自己实现了一套Buffer框架来满足自己的业务或者性能需求。
ByteBuf的优点:
- 可以自定义的缓冲区类型扩展
- 通过内置的复合缓冲区类型实现了透明的零拷贝
- 容量可以按需增长(类似于 JDK 的 StringBuilder)
- 在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip()方法
- 读和写使用了不同的索引
- 支持方法的链式调用
- 支持引用计数
- 支持池化
ByteBuf如何工作
ByteBuf称作Netty的数据容器,如下,一个ByteBuf被两个pointers分成三个区域。
+-------------------+------------------+------------------+
| discardable bytes | readable bytes | writable bytes |
| | (CONTENT) | |
+-------------------+------------------+------------------+
| | | |
0 <= readerIndex <= writerIndex <= capacity
3个区域:废弃区,可读区和可写区
- 在初始化状态下,readerIndex和writerIndex都为0,整个空间中只存在可写区。此时只能写,不能读,进行读操作会抛出异常。
- 写字节后,writeIndex增加,readIndex不变。
- 读字节时,writeIndex不变,readIndex增加。
- 如果discardable区域被回收,两个pointer都向左平移。
- 如果调用clear(),两个pointer都回到0位置。
- readerIndex > writerIndex, 将会抛异常IndexOutOfBoundsException。
- writerIndex 超过最大容量也会触发异常
当然,实际上除了数据负载外,还需要存储各种属性值,如http响应的状态码和cookie等。Netty提供了ByteBufHolder,其中content()返回了所持有的ByteBuf。
字节级操作
名称以 read 或者 write 开头的 ByteBuf 方法,将会推进其对应的索引,而名称以set或者get开头的操作则不会。这个很关键,用错了可能导致一直读取同一份数据。 读
ByteBuf buffer = ...;
for (int i = 0; i < buffer.capacity(); i++) {
byte b = buffer.getByte(i);// 不会改变index
System.out.println((char)b);
}
ByteBuf buffer = ...;
while (buffer.isReadable()) {
System.out.println(buffer.readByte());
}
写
while (buffer.maxWritableBytes() >= 4) {
buffer.writeInt(random.nextInt());
}
查找
ByteBuf buffer = ...;
int index = buffer.forEachByte(ByteProcessor.FIND_CR);
还可以派生出一个新的ByteBuf实例,或者完全复制出一个新副本。切片是共享的,copy不共享,完全独立的副本。
ByteBuf的特殊机制
buffer池:减少分配释放的压力,
零拷贝:
是指计算机操作的过程中,CPU不需要为数据在内存之间的拷贝消耗资源。如计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。Netty通过ByteBuf.slice以及Unpooled.wrappedBuffer等方法拆分、合并Buffer无需拷贝数据。
参考文档: