了解 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 回收