网络数据的基本单位是字节,JavaNIO提供了ByteBuffer作为它的字节容器,但是这个类过于复杂,所以netty使用ByteBuf解决了ByteBuffer的局限性,提供了更好的API。
Netty的数据处理API通过两个组件暴露–abstract class ByteBuf和Interface ByteBufHolder。
下面是ByteBuf API的优点:
- 它可以被用户自定义的缓冲区类型扩展
- 通过内置的复合缓冲区类型实现了透明的零拷贝
- 容量可以按需增长
- 在读与写这两种模式之间切换不需要调用ByteBuffer的flip()方法
- 读和写使用了不同的索引
- 支持方法的链式调用
- 支持引用计数
- 支持池化
ByteBuf维护了2个索引:一个用于读取,一个用于写入。
ByteBuf的使用模式:
1.堆缓冲区
最常用的ByteBuf模式是将数据存储在JVM的堆空间中。这种模式成为支撑数组,它能在没有池化的情况下提供快速的分配和释放。非常适合有遗留的数据需要处理的情况。
2.直接缓冲区
直接缓冲区,使用的内存来自堆外,有点是少一次复制,缺点是分配和释放都较为昂贵。
3.复合缓冲区
将多个缓冲区表示为单个合并缓冲区的虚拟表示。消除了没必要的复制,弥补了JDK的缺失。
CompositeByteBuf实现了这个模式。
ByteBuf实现了ReferenceCounted和java.lang.Comparable< ByteBuf>接口,
一般通过Unpooled 创建一个ByteBuf实例,而不是调用各自的构造方法。
字节级操作:
1.随机访问索引
buffer.getByte(i),不会改变读索引,可以使用readerIndex(i)来修改索引。
2.顺序访问索引
3.可丢弃字节
可以使用discardReadBytes()方法丢弃并回收空间,可写空间会扩展,但是这个方法可能导致内存复制,所以除非真的需要,否则不用。
4.可读字节
以read或skip开头的操作都将检索或者跳过位于当前readerIndex的数据,并增加已读字节数。
5.索引管理
可以通过调用 markReaderIndex()、 markWriterIndex()、 resetWriterIndex()和 resetReaderIndex()来标记和重置 ByteBuf 的 readerIndex 和 writerIndex。
也可以通过调用 readerIndex(int)或者 writerIndex(int)来将索引移动到指定位置。
可以通过调用 clear()方法来将 readerIndex 和 writerIndex 都设置为 0。注意, 这并不会清除内存中的内容。
6.查找操作
在ByteBuf中有多种可以用来确定指定值的索引的方法。最简单的是使用indexOf()方法。
较复杂的查找可以通过那些需要一个ByteBufProcessor①
ByteProcessor针对一些常见的值定义了许多便利的方法。假设你的应用程序需要和所谓的包含有以NULL结尾的内容的Flash套接字作为参数的方法达成。
7.派生缓冲区
派生缓冲区为ByteBuf提供了专门的方式来呈现其内容的视图。这类视图通过以下方法被创建:
- duplicate
- slice
- slice
- Unpooled.unmodifiableBuffer
- order
- readSlice
每个这些方法都将返回一个新的 ByteBuf 实例,它具有自己的读索引、写索引和标记索引。 其内部存储和 JDK 的 ByteBuffer 一样也是共享的。这使得派生缓冲区的创建成本是很低廉的,但是这也意味着,如果你修改了它的内容,也同时修改了其对应的源实例,所
以要小心。
8.读写操作
8.其它有用操作
名称 | 描述 |
---|---|
isReadable() | 如果至少有一个字节可供读取,则返回 true |
isWritable() | 如果至少有一个字节可被写入,则返回 true |
readableBytes() | 返回可被读取的字节数 |
writableBytes() | 返回可被写入的字节数 |
capacity() | 返回 ByteBuf 可容纳的字节数。在此之后,它会尝试再次扩展直到达到 maxCapacity() |
writableBytes() | 返回可被写入的字节数 |
maxCapacity() | 返回 ByteBuf 可以容纳的最大字节数 |
hasArray() | 如果 ByteBuf 由一个字节数组支撑,则返回 true |
array() | 如果 ByteBuf 由一个字节数组支撑则返回该数组;否则,它将抛出一个UnsupportedOperationException 异常 |
ByteBufHolder接口
ByteBufHolder 为 Netty 的高级特性提供了支持,如缓冲区池化,其中可以从池中借用 ByteBuf, 并且在需要时自动释放。
declare的方法:
ByteBuf分配:
1.按需分配:ByteBufAllocator接口
为了降低分配和释放内存的开销,netty通过interface ByteBufAllocator实现了ByteBuf的池化,
可以通过 Channel(每个都可以有一个不同的 ByteBufAllocator 实例)或者绑定到ChannelHandler 的 ChannelHandlerContext 获取一个到 ByteBufAllocator 的引用。
Netty提供了两种ByteBufAllocator的实现: PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片。此实现使用了一种称为jemalloc的已被大量现代操作系统所采用的高效方法来分配内存。后者的实现不池化ByteBuf实例, 并且在每次它被调用时都会返回一个新的实例。
2.Unpooled缓冲区
可能某些情况下,你未能获取一个到 ByteBufAllocator 的引用。对于这种情况, Netty 提供了一个简单的称为 Unpooled 的工具类, 它提供了静态的辅助方法来创建未池化的 ByteBuf实例。
3.ByteBufUtil 类
ByteBufUtil 提供了用于操作 ByteBuf 的静态的辅助方法。因为这个 API 是通用的, 并且和池化无关,所以这些方法已然在分配类的外部实现。
这些静态方法中最有价值的可能就是 hexdump()方法, 它以十六进制的表示形式打印ByteBuf 的内容。
引用计数
引用计数是一种通过在某个对象所持有的资源不再被其他对象引用时释放该对象所持有的资源来优化内存使用和性能的技术。 Netty 在第 4 版中为 ByteBuf 和ByteBufHolder 引入了引用计数技术,它们都实现了 interface ReferenceCounted。