了解 Netty 中的 ByteBuf 类吗?
答:
在 Java NIO 编程中,Java 提供了 ByteBuffer 作为字节缓冲区类型(缓冲区可以理解为一段内存区域),来表示一个连续的字节序列。
Netty 中并没有使用 Java 的 ByteBuffer,而是使用了新的缓冲类型 ByteBuf,特性如下:
-
允许自定义缓冲类型
-
复合缓冲类型中内置的透明的零拷贝实现
-
开箱即用的动态缓冲类型,具有像 StringBuffer 一样的动态缓冲能力
-
不再需要调用 flip() 方法
Java 的 ByteBuffer 类中,需要使用 flip() 来进行读写两种模式的切换
-
正常情况下具有比 ByteBuffer 更快的响应速度
Java 中的 ByteBuffer:
主要需要注意有 3 个属性:position、limit、capacity
- capacity:当前数组的容量大小
- position:写入模式的可写入数据的下标,读取模式的可读取数据下标
- limit:写入模式的可写入数组大小,读取模式的最多可以读取数据的下标
假如说数组容量是 10,那么三个值初始值为:
position = 0
limit = 10
capacity = 10
假如写入 4 个字节的数据,此时三个值如下:
position = 4
limit = 10
capacity = 10
如果切换到读取数据模式(使用 flip()
),会改变上边的三个值,会从 position 的位置开始读取数据到 limit 的位置
position = 0
limit = 4
capacity = 10
Netty 中的 ByteBuf:
ByteBuf 主要使用两个指针来完成缓冲区的读写操作,分别是: readIndex
和 writeIndex
- 当写入数据时,writeIndex 会增加
- 当读取数据时,readIndex 会增加,但不会超过 writeIndex
ByteBuf 的使用:
public static void main(String[] args) {
ByteBuf buffer = Unpooled.buffer(10);
System.out.println("----------初始化ByteBuf----------");
printByteBuffer(buffer);
System.out.println("----------ByteBuf写入数据----------");
String str = "hello world!";
buffer.writeBytes(str.getBytes());
printByteBuffer(buffer);
System.out.println("----------ByteBuf读取数据----------");
while (buffer.isReadable()) {
System.out.print((char)buffer.readByte());
}
System.out.println();
printByteBuffer(buffer);
System.out.println("----------ByteBuf释放无用空间----------");
buffer.discardReadBytes();
printByteBuffer(buffer);
System.out.println("----------ByteBuf清空----------");
buffer.clear();
printByteBuffer(buffer);
}
private static void printByteBuffer(ByteBuf buffer) {
System.out.println("readerIndex:" + buffer.readerIndex());
System.out.println("writerIndex:" + buffer.writerIndex());
System.out.println("capacity:" + buffer.capacity());
}
/**输出**/
----------初始化ByteBuf----------
readerIndex:0
writerIndex:0
capacity:10
----------ByteBuf写入数据----------
readerIndex:0
writerIndex:12
capacity:64
----------ByteBuf读取数据----------
hello world!
readerIndex:12
writerIndex:12
capacity:64
----------ByteBuf释放无用空间----------
readerIndex:0
writerIndex:0
capacity:64
----------ByteBuf清空----------
readerIndex:0
writerIndex:0
capacity:64
ByteBuf 的 3 种使用模式:
ByteBuf 共有 3 种使用模式:
-
堆缓冲区模式(Heap Buffer)
堆缓冲区模式又称为 “支撑数据”,其数据存放在 JVM 的
堆空间
优点:
- 数据在 JVM 堆中存储,可以快速创建和释放,并且提供了数组直接快速访问的方法
缺点:
- 每次数据与 IO 进行传输时,都需要将数据复制到直接缓冲区(这里为什么要将数据复制到直接缓冲区的原因在上边的
直接内存比堆内存快在了哪里?
问题中已经讲过)
创建代码:
ByteBuf buffer = Unpooled.buffer(10);
-
直接缓冲区模式(Direct Buffer)
直接缓冲区模式属于堆外分配的直接内存,不占用堆的容量
优点:
- 使用 socket 传输数据时性能很好,避免了数据从 JVM 堆内存复制到直接缓冲区
缺点:
- 相比于堆缓冲区,直接缓冲区分配内存空间和释放更为昂贵
创建代码:
ByteBuf buffer = Unpooled.directBuffer(10);
-
复合缓冲区模式(Composite Buffer)
本质上类似于提供一个或多个 ByteBuf 的组合视图
优点:
- 提供一种方式让使用者自由组合多个 ByteBuf,避免了复制和分配新的缓冲区
缺点:
- 不支持访问其支撑数据,如果要访问,需要先将内容复制到堆内存,再进行访问
创建代码:
public static void main(String[] args) { // AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Test.class); // 创建一个堆缓冲区 ByteBuf heapBuf = Unpooled.buffer(2); String str1 = "hi"; heapBuf.writeBytes(str1.getBytes()); // 创建一个直接缓冲区 ByteBuf directBuf = Unpooled.directBuffer(5); String str2 = "nihao"; directBuf.writeBytes(str2.getBytes()); // 创建一个复合缓冲区 CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer(10); compositeByteBuf.addComponents(heapBuf, directBuf); // 检查是否支持支撑数组,发现并不支持 if (!compositeByteBuf.hasArray()) { for (ByteBuf buf : compositeByteBuf) { // 第一个字节偏移量 int offset = buf.readerIndex(); // 总共数据长度 int length = buf.readableBytes(); byte[] bytes = new byte[length]; // 不支持访问支撑数组,需要将内容复制到堆内存中,即 bytes 数组中,才可以进行访问 buf.getBytes(offset, bytes); printByteBuffer(bytes, offset, length); } } } private static void printByteBuffer(byte[] array, int offset, int length) { System.out.println("array:" + array); System.out.println("array->String:" + new String(array)); System.out.println("offset:" + offset); System.out.println("len:" + length); } /**输出**/ array:[B@4f8e5cde array->String:hi offset:0 len:2 array:[B@504bae78 array->String:nihao offset:0 len:5
Netty 中 ByteBuf 如何分配?有池化的操作吗?
答:
ByteBuf 的分配接口定义在了 ByteBufAllocator
中,他的直接抽象类是 AbstractByteBufAllocator
,而 AbstractByteBufAllocator
有两种实现:PooledByteBufAllocator
和 UnpooledByteBufAllocator
- PooledByteBufAllocator 提供了池化的操作,将 ByteBuf 实例放入池中,提升了性能,将内存碎片化减到了最小UnpooledByteBufAllocator。(这个实现采用了一种内存分配的高效策略,成为 jemalloc,已经被好几种现代操作系统所采用)
- UnpooledByteBufAllocator 在每次创建缓冲区时,都会返回一个新的 ByteBuf 实例,这些实例由 JVM 负责 gc 回收