简介
ByteBuf
是Netty的数据容器,它解决了JDK API的局限性,能为网络应用程序的开发者提供更好的API支持。ByteBuf
API的优点如下:
- 它可以被用户自定义的缓冲区类型拓展
- 通过内置的复合缓冲区类型实现了透明的零拷贝。
- 容量可以按需增长
- 在读和写这两种模式下切换不需要调用
BuyteBuffer
的flip()
方法 - 读和写使用了不同的索引
- 方式支持链式调用
- 支持引用计数
- 支持池化
工作原理
ByteBuf
内部维护了两个不同的索引,一个用于读取,一个用于写入。
使用模式
堆缓冲区
最常见的ByteBuf
模式,是将数据存储到JVM的堆空间中。这种模式被称为支持数组。它能够在没有使用池化的情况下,提供较为快速的分配和释放。
1 | ByteBuf heapBuf = ...; |
直接缓冲区
直接缓冲区是指内存空间是通过本地调用分配而来的。因此直接缓冲区的内容将驻留在堆外。
通过使用直接缓冲区,避免了依次将JVM堆中的缓冲区复制到直接缓冲区的国产,因此效率更高。但是直接缓冲区的创建和释放的成本比较高。
1 | ByteBuf directBuf = ...; |
复合缓冲区
复合缓冲区可以为多个ByteBuf
提供一个聚合视图。我们可以根据需要添加或删除ByteBuf
实例。Netty通过ByteBuf
的一个子类CompositeByteBuf
来实现复合缓冲区。
1 | public static void byteBufComposite() { |
字节级操作
随机访问索引
和普通的Java数组一样,ByteBuf
的索引也是从零开始的。
1 | ByteBuf buffer = ...; |
顺序访问索引
ByteBuf
同时具有读索引和写索引,因此两个索引把ByteBuf
分为了三个部分。分贝是可丢弃字节区,可读字节区,可写字节区。
查找操作
查找ByteBuf
指定的指。可以利用indexOf()
来直接查询,也可以利用ByteProcessor
作为参数来查找某个指定的值。
1 | public static void byteProcessor() { |
派生缓冲区
派生缓冲区为ByteBuf
提供了以专门的方式来呈现其内容的视图,这类视图是通过以下方法被创建的:
1 | duplicate(); |
ByteBufHolder接口
ByteBufHolder
是ByteBuf
的容器,可以通过子类实现ByteBufHolder
接口,根据自身需要添加自己需要的数据字段。可以用于自定义缓冲区类型扩展字段。ByteBufHolder
接口提供了几种用于访问底层数据和引用计数的方法。
1 | content();//返回持有的所有的ByteBuf |
ByteBuf分配
按需分配ByteBufAllocator接口
为了降低分配和释放内存的开销,Netty通过ByteBufAllocator
实现ByteBuf
池化。
如何获取ByteBufAllocator
实例:
1 | Channel channel = ...; |
Unpooled缓冲区
在不能获取到ByteBufAllocator
中情况下,可以使用Unpooled
获取缓冲区。
引用计数
引用计数是一种通过在某个对象所持有的资源不再被其它对象引用时释放该对象所持有的资源来优化内存使用和性能的计数。
一个ReferencceCounted
实现的实例通常以活动的引用计数为1作为开始。只要引用计数大于0,旧能保证对象不会被释放。当活动引用的数量减少到0时,该实例就会被释放。
1 | ByteBuf buffer = ... |
零拷贝
零拷贝是指在操作数据的时候,不需要将数据buffer从一个内存区域拷贝到另一个内存区域,因为少了一次内存的拷贝,因此CPU的效率就得到了较大的提升。
OS层面的零拷贝
OS层面的零拷贝通常是指避免在用户态与内核态之间来回进行数据拷贝。比如Linux提供了mmap
系统调用,它可以将用户内存空间映射到内核空间。这样用户对这段内存空间的操作就可以直接反映到内核。
Netty层面的零拷贝
Netty层面的零拷贝主要体现在这几个方法:
- 提供了
CompositeByteBuf
,它可以将多个ByteBuf
合并为一个逻辑上的ByteBif
,避免了ByteBuf
之间的拷贝。 通过
wrap
操作,我们可以将byte[]
数组,ByteBuf
、ByteBuffer
包装为一个ByteBuf
对象,进而避免了拷贝操作。1
2byte[] bytes = ...
ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);ByteBuf
支持slice
操作, 因此可以将ByteBuf
分解为多个共享同一个存储区域的ByteBuf
, 避免了内存的拷贝.1
2
3ByteBuf byteBuf = ...
ByteBuf header = byteBuf.slice(0, 5);
ByteBuf body = byteBuf.slice(5, 10);通过
FileRegion
包装的FileChannel.tranferTo
实现文件传输, 可以直接将文件缓冲区的数据发送到目标Channel
, 避免了传统通过循环 write 方式导致的内存拷贝问题.1
2
3
4
5
6
7
8
9
10RandomAccessFile srcFile = new RandomAccessFile(srcFileName, "r");
FileChannel srcFileChannel = srcFile.getChannel();
RandomAccessFile destFile = new RandomAccessFile(destFileName, "rw");
FileChannel destFileChannel = destFile.getChannel();
long position = 0;
long count = srcFileChannel.size();
//直接传输,而不是通过while进行循环的
srcFileChannel.transferTo(position, count, destFileChannel);
v1.5.2