netty的数据容器
网络数据的基本单位大多为字节,Java NIO 提供了ByteBuffer 作为它的字节容器,但使用起来过于复杂和繁琐。在Netty中, ByteBuffer 替代品是ByteBuf,一个强大的实现,既解决了JDK API 的局限性,又为网络应用程序的开发者提供了更好的API。由不同的索引分别控制读访问和写访问的字节数组。
ByteBuf
Netty 的数据处理API 通过两个组件暴露给用户,它们是 abstract class ByteBuf 和interface ByteBufHolder。
下面是一些ByteBuf API 的优点:
1. 可以被用户自定义的缓冲区类型扩展;
2. 通过内置的复合缓冲区类型实现了透明的零拷贝;
3. 容量可以按需增长(类似于JDK 的StringBuilder);
4. 在读和写这两种模式之间切换不需要调用ByteBuffer 的flip()方法;
5. 读和写使用了不同的索引;
6. 支持方法的链式调用;
7. 支持引用计数;
8. 支持池化。
ByteBuf 的使用模式
堆缓冲区:将数据存储在JVM 的堆空间中,使用数组实现。
堆缓冲的优点是:由于数据存储在JVM的堆中可以快速创建和快速释放,并且提供了数组的直接快速访问的方法。
堆缓冲缺点是:每次读写数据都要先将数据拷贝到直接缓冲区再进行传递。
直接缓冲区:NIO 在JDK 1.4 中引入的ByteBuffer 类允许JVM 实现通过本地调用来分配内存。
这主要是为了避免在每次调用本地I/O 操作之前(或者之后)将缓冲区的内容复制到一个中间缓冲区(或者从中间缓冲区把内容复制到缓冲区)。ByteBuffer的 Javadoc明确指出:“直接缓冲区的内容将驻留在常规的会被垃圾回收的堆之外。直接缓冲区的主要缺点是,相对于基于堆的缓冲区,它们的分配和释放都较为 昂贵。在处理遗留代码时,因为数据不在堆上,所以需要将数据全部复制
复合缓冲区:复合缓冲区就类似于一个ByteBuf的组合视图,在这个视图里面我们可以创建不同的ByteBuf(可以是不同类型的)。 这样,复合缓冲区就类似于一个列表,我们可以动态的往里面添加和删除其中的ByteBuf。
ByteBufHolder接口:存储ByteBfu的各种属性值
ByteBuf的分配:
按需分配:ByteBufAllocator 接口
可以通过Channel(每个都可以有一个不同的ByteBufAllocator 实例)或者绑定到ChannelHandler 的ChannelHandlerContext 获取一个到ByteBufAllocator 的引用。
Netty提供了两种ByteBufAllocator的实现:PooledByteBufAllocator和Unpooled-ByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片。此实
现使用了一种称为jemalloc的已被大量现代操作系统所采用的高效方法来分配内存。后者的实现不池化ByteBuf实例,并且在每次它被调用时都会返回一个新的实例。
虽然Netty默认使用了PooledByteBufAllocator,但这可以很容易地通过Channel-Config API或者在应用程序中指定一个不同的分配器来更改。
Unpooled 缓冲区:
它提供了静态的辅助方法来创建未池化的ByteBuf实例。
ByteBufUtil 类:
ByteBufUtil 提供了用于操作ByteBuf 的静态的辅助方法。因为这个API 是通用的,并且和池化无关,所以这些方法已然在分配类的外部实现。
引用计数:当指向某个对象的引用数目为0时,该对象所占资源将被回收。
引用计数对于池化实现(如PooledByteBufAllocator)来说是至关重要的,它降低了内存分配的开销。
要点:
1. 使用不同的读索引和写索引来控制数据访问;
2. 使用内存的不同方式——基于字节数组和直接缓冲区;
3. 通过CompositeByteBuf 生成多个ByteBuf 的聚合视图;
4. 数据访问方法——搜索、切片以及复制;
5. 读、写、获取和设置API;
6. ByteBufAllocator 池化和引用计数。