Netty使用ByteBuf替换Java NIO提供的ByteBuffer(使用过于复杂).
ByteBuf 的API
下面是一些ByteBuf API 的优点:
- 它可以被用户自定义的缓冲区类型扩展;
- 通过内置的复合缓冲区类型实现了透明的零拷贝;
- 容量可以按需增长(类似于JDK 的StringBuilder);
- 在读和写这两种模式之间切换不需要调用ByteBuffer 的flip()方法;
- 读和写使用了不同的索引;
- 支持方法的链式调用;
- 支持引用计数;
- 支持池化。
ByteBuf如何工作的
一个读索引和写索引都设置为0 的16 字节ByteBuf
名称以read 或者write 开头的ByteBuf 方法,将会推进其对应的索引,而名称以set 或者get 开头的操作则不会
ByteBuf 的使用模式
- 堆缓冲区
最常用的ByteBuf 模式是将数据存储在JVM 的堆空间中。这种模式被称为支撑数组(backing array),它能在没有使用池化的情况下提供快速的分配和释放。
ByteBuf heapBuf = ...;
if (heapBuf.hasArray()) { // 检查ByteBuf 是否有一个支撑数组
byte[] array = heapBuf.array(); // 如果有,则获取对该数组的引用
int offset = heapBuf.arrayOffset() + heapBuf.readerIndex(); // 计算第一个字节的偏移量。
int length = heapBuf.readableBytes(); // 获得可读字节数
handleArray(array, offset, length); // 使用数组、偏移量和长度作为参数调用你的方法
}
- 直接缓冲区
直接缓冲区的内容将驻留在常规的会被垃圾回收的堆之外.
ByteBuf directBuf = ...;
if (!directBuf.hasArray()) { // 检查ByteBuf 是否由数组支撑。如果不是,则这是一个直接缓冲区
int length = directBuf.readableBytes(); // 获取可读字节数
byte[] array = new byte[length]; // 分配一个新的数组来保存具有该长度的字节数据
directBuf.getBytes(directBuf.readerIndex(), array); // 将字节复制到该数组
handleArray(array, 0, length); // 使用数组、偏移量和长度作为参数调用你的方法
}
- 复合缓冲区
为多个ByteBuf 提供一个聚合视图。在这里你可以根据需要添加或者删除ByteBuf 实例.子类CompositeByteBuf实现.
持有一个头部和主体的CompositeByteBuf
字节级操作
- 随机访问索引
ByteBuf 的索引是从零开始的:第一个字节的索引是0,最后一个字节的索引总是capacity() - 1。 - 顺序访问索引
两个索引划分成3 个区域.
ByteBuf 的内部分段
- 可丢弃字节
可丢弃字节的分段包含了已经被读过的字节。通过调用discardRead-Bytes()方法,可以丢弃它们并回收空间
丢弃已读字节之后的ByteBuf
- 可读字节
任何名称以read 或者skip 开头的操作都将检索或者跳过位于当前readerIndex 的数据,并且将它增加已读字节数。 - 可写字节
任何名称以write 开头的操作都将从当前的writerIndex 处开始写数据,并将它增加已经写入的字节数 - 索引管理
通过调用markReaderIndex()、markWriterIndex()、resetWriterIndex()和resetReaderIndex()来标记和重置ByteBuf 的readerIndex 和writerIndex。可以通过调用clear()方法来将readerIndex 和writerIndex 都设置为0.
clear()方法被调用之前
在clear()方法被调用之后
- 查找操作
boolean process(byte value) 检查输入值是否是正在查找的值
forEach Byte(ByteBufProcessor.FIND_NUL) 查找null - 派生缓冲区
1.duplicate();
2.slice();
3.slice(int, int);
4.Unpooled.unmodifiableBuffer(…);
5.order(ByteOrder);
6.readSlice(int)。
返回新的ByteBuf 实例,如果你修改了它的内容,也同时修改了其对应的源实例,所以要小心。如果需要一个现有缓冲区的真实副本,请使用copy()或者copy(int, int)
- 读/写操作
有两种类别的读/写操作:
get()和set()操作,从给定的索引开始,并且保持索引不变;
read()和write()操作,从给定的索引开始,并且会根据已经访问过的字节数对索引进行调整。
ByteBufHolder 接口
content() 返回由这个ByteBufHolder 所持有的ByteBuf
copy() 返回这个ByteBufHolder 的一个深拷贝,包括一个其所包含的ByteBuf 的非共享拷贝
duplicate() 返回这个ByteBufHolder 的一个浅拷贝,包括一个其所包含的ByteBuf 的共享拷贝
ByteBuf 分配
- 按需分配:ByteBufAllocator 接口
Netty 通过interface ByteBufAllocator 实现了(ByteBuf 的)池化
PooledByteBufAllocator和Unpooled-ByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片(一种称为jemalloc的已被大量现代操作系统所采用的高效方法来分配内存).后者的实现不池化ByteBuf实例,并且在每次它被调用时都会返回一个新的实例. - Unpooled 缓冲区
buffer()
buffer(int initialCapacity)
buffer(int initialCapacity, int maxCapacity)
返回一个未池化的基于堆内存存储的
ByteBuf
directBuffer()
directBuffer(int initialCapacity)
directBuffer(int initialCapacity, int maxCapacity)
返回一个未池化的基于直接内存存储
的ByteBuf
wrappedBuffer() 返回一个包装了给定数据的ByteBuf
copiedBuffer() 返回一个复制了给定数据的ByteBuf
- ByteBufUtil 类
hexdump() 以十六进制的表示形式打印ByteBuf 的内容
equals(ByteBuf, ByteBuf) 它被用来判断两个ByteBuf实例的相等性 - 引用计数
引用计数对于池化实现(如PooledByteBufAllocator)来说是至关重要的,它降低了内存分配的开销。