Netty实战读书笔记 - ByteBuf
1. Byte的API
Netty的数据处理API通过两个组件暴露: abstract class ByteBuf 和 interface ByteBufHolder。
2. ByteBuf类
2.1 它是如何工作的
ByteBuf维护了两个不同的索引:一个用于读取,一个用于写入。当你从ByteBuf读取时,它的 readerIndex 将会被递增已经被读取的字节数。同样的,当你写入 ByteBuf 时, 它的 writerIndex 也会被递增。
名称以 read 或 write 开头的ByteBuf方法,将会推进其对应的索引, 而名称以 set 或者 get 开头的操作则不会。
2.2. ByteBuf的使用模式
2.2.1 堆缓冲区
最常用的ByteBuf模式是将数据存储在JVM的堆空间中。这种模式被称为支撑数组(backing array),它能在没有使用池化的情况下提供快速的分配和释放。
2.2.2 直接缓冲区
ByteBuffer的Javadoc明确指出:“直接缓冲区的内容将驻留在常规的会被垃圾回收的堆之外。”这也就解释了为何直接缓冲区对于网络数据传输是理想的选择。如果你的数据包含在一个在堆上分配的缓冲区中,那么事实上,在通过套接字发送它之前,JVM将会在内部把你的缓冲区复制到一个直接缓冲区中。
A direct byte buffer may be created by invoking the allocateDirect factory method of this class. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious.
直接缓冲区的主要缺点是,相对于基于堆的缓冲区,它们的分配和释放都较为昂贵。如果你正在处理遗留代码,你可可能会遇到另外一个缺点:因为数据不是在堆上,所以你不得不进行一次复制。
2.2.3 符合缓冲区
第三种也是最后一种模式使用的是复合缓冲区,他为多个ByteBuf提供一个聚合视图。
3. 字节级操作
3.1 派生缓冲区
派生缓冲区为 ByteBuf 提供了以专门的方式来呈现其内容的视图。 这类视图是通过以下方法被创建的:
- duplicate()
- slice()
- slice(int, int)
- Unpooled.unmodifiableBuffer(…)
- order(ByteOrder)
- readSlice(int)
每个这些方法都将返回一个新的ByteBuf实例,它具有自己的读索引、写索引和标记索引。其内部存储和JDK的ByteBuffer一样也是共享的。这使得派生缓冲区的成本是很低廉的,但是这也意味着,如果你修改了它的内容,也同时修改了其对应的源实例,所以要小心。
ByteBuf复制
如果需要一个现有缓冲区的真实副本,请使用 copy() 或 copy(int, int)方法。不同于派生缓冲区,由这个调用所返回的 ByteBuf 拥有独立的数据副本。
4. ByteBuf 分配
Netty提供了两种 ByteBufAllocator的实现: PooledByteBufAllocator 和 Unpooled-ByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度的减少内存碎片。此实现使用了一种称为 jemalloc 的已被大量现代操作系统所采用的高效方法来分配内存。 后者的实现不池化ByteBuf实例,并且在每次它被调用时都会返回一个新的实例。
ByteBufUtil提供了用于操作ByteBuf的静态辅助方法。因为这个API是通用的,并且和池化无关,所以这些方法已然在分配类的外部实现。
5. 引用计数
引用计数是一种通过在某个对象所持有的资源不再被其他对象引用时释放该对象所持有的资源来优化内存使用和性能的技术。