李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
09.Netty之ByteBuf介绍(一)
Leefs
2022-06-11 PM
901℃
0条
[TOC] ### 一、概述 ByteBuf是对NIO中ByteBuffer的增强。相比于ByteBuffer,Netty 的 ByteBuf 具有卓越的功能性和灵活性。 **ByteBuf API 的优点** + 可以被用户自定义的缓冲区类型扩展 + 通过内置的复合缓冲区类型实现透明的零拷贝 + 容量可以按需增长 + 在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip() 方法 + 在读和写使用了不同的索引 + 支持方法的链式调用 + 支持引用计数 + 支持池化 ### 二、工作原理 ByteBuf 维护了两个不同的索引:一个用于读取,一个用于写入,当你从 ByteBuf 读取时,readIndex 会递增已经被读取的字节数。同样的,当你写入 ByteBuf 时,它的 writeIndex 也会递增。 ![09.Netty之ByteBuf介绍(一)01.png](https://lilinchao.com/usr/uploads/2022/06/121746136.png) readIndex 和 writeIndex 的起始位置都为 0。 如果 readIndex 和 writeIndex 的值相等,也即此时已经到了可读取数据的末尾,就如同达到数组末尾一样,试图读取超出该点的数据将触发 IndexOutOfBoundsException。 名称以read或者write开头的ByteBuf方法,将会推进其对应的索引,而名称以set或者get开关的操作则不会。可以指定ByteBuf的最大容量。(默认的限制是Integer.MAX_VALUE)。 ### 三、ByteBuf创建 可以通过ByteBuf Allocator类中的默认实现来创建一个Bytebuf ```java import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; public class ByteBufCreateDemo { public static void main(String[] args) { //在传参时可以指定ByteBuf的容量,如果不指定的默认是256 // ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(10); ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); } } ``` + 在创建ByteBuf时可以指定容量大小,如果不指定的默认是256 相比于NIO中的ByteBuffer,Netty中的Bytebuf可以动态扩容。 + **验证动态扩容** ```java import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; /** * Created by lilinchao * Date 2022/6/11 * Description ByteBuf验证动态扩容 */ public class ByteBufCreateDemo { public static void main(String[] args) { // 创建一个Bytebuf,默认创建的容量是256 ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); System.out.println("添加数据前:" + buffer); // 往Bytebuf中写数据 StringBuilder stringBuilder = new StringBuilder(); // 故意超过初始容量,验证是否会自动扩容 for (int i = 0; i < 300; i++) { stringBuilder.append("a"); } // 将数据写入ByteBuf buffer.writeBytes(stringBuilder.toString().getBytes()); System.out.println("添加数据后:" + buffer); } } ``` **运行结果** ``` 添加数据前:PooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 256) 添加数据后:PooledUnsafeDirectByteBuf(ridx: 0, widx: 300, cap: 512) ``` **说明** + **ridx**:表示read index,即读取位置; + **widx**:表示write index,即写入位置; + **cap**:表示容量 从程序运行结果可知,将数据写入ByteBuf后容量由之前的256扩容到512,扩大了一倍。 ### 四、ByteBuf的使用模式 **ByteBuf共有三种模式:** + 堆缓冲区模式(Heap Buffer) ```java //可以使用下面的代码来创建池化基于堆的 ByteBuf ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(16); ``` + 直接缓冲区模式(Direct Buffer) ```java //通过该方法创建的ByteBuf,使用的是基于直接内存的ByteBuf ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10); ``` + 复合缓冲区模式(Composite Buffer) #### 4.1 堆缓冲区模式(Heap Buffer) 堆缓冲区模式又称为**支撑数组(backing array)**。将数据存放在JVM的堆空间,通过将数据存储在数组中实现。 - **优点**: 由于数据存储在JVM堆中可以快速创建和快速释放,并且提供了数组直接快速访问的方法 - **缺点**: 每次数据与I/O进行传输时,都需要将数据拷贝到直接缓冲区 ```java public class ByteBufHeapBufferDemo { public static void main(String[] args) { // 创建一个堆缓冲区 ByteBuf buffer = Unpooled.buffer(10); String s = "waylau"; buffer.writeBytes(s.getBytes()); // 检查是否是支撑数组 if (buffer.hasArray()) { // 获取支撑数组的引用 byte[] array = buffer.array(); // 计算第一个字节的偏移量 int offset = buffer.readerIndex() + buffer.arrayOffset(); // 可读字节数 int length = buffer.readableBytes(); printBuffer(array, offset, length); } } //打印出Buffer的信息 private static void printBuffer(byte[] array, int offset, int len) { System.out.println("array:" + array); System.out.println("array->String:" + new String(array)); System.out.println("offset:" + offset); System.out.println("len:" + len); } } ``` **运行结果** ``` array:[B@5b37e0d2 array->String:waylau offset:0 len:6 ``` #### 4.2 直接缓冲区模式(Direct Buffer) Direct Buffer属于堆外分配的直接内存,不会占用堆的容量。适用于套接字传输过程,避免了数据从内部缓冲区拷贝到直接缓冲区的过程,性能较好。 - **优点**: 使用Socket传递数据时性能很好,避免了数据从Jvm堆内存拷贝到直接缓冲区的过程。提高了性能 - **缺点:** 相对于堆缓冲区而言,Direct Buffer分配内存空间和释放更为昂贵 ```java public class ByteBufDirectBufferDemo { public static void main(String[] args) { // 创建一个直接缓冲区 ByteBuf buffer = Unpooled.directBuffer(10); String s = "waylau"; buffer.writeBytes(s.getBytes()); // 检查是否是支撑数组. // 不是支撑数组,则为直接缓冲区 if (!buffer.hasArray()) { // 计算第一个字节的偏移量 int offset = buffer.readerIndex(); // 可读字节数 int length = buffer.readableBytes(); // 获取字节内容 byte[] array = new byte[length]; buffer.getBytes(offset, array); printBuffer(array, offset, length); } } //打印出Buffer的信息 private static void printBuffer(byte[] array, int offset, int len) { System.out.println("array:" + array); System.out.println("array->String:" + new String(array)); System.out.println("offset:" + offset); System.out.println("len:" + len); } } ``` **运行结果** ```java array:[B@6d5380c2 array->String:waylau offset:0 len:6 ``` 对于涉及大量I/O的数据读写,建议使用Direct Buffer。而对于用于后端的业务消息编解码模块建议使用Heap Buffer。 #### 4.3 复合缓冲区模式(Composite Buffer) Composite Buffer是Netty特有的缓冲区。本质上类似于提供一个或多个ByteBuf的组合视图,可以根据需要添加和删除不同类型的ByteBuf。 + **优点**:提供了一种访问方式让使用者自由地组合多个`ByteBuf`,避免了复制和分配新的缓冲区。 + **缺点**:不支持访问其支撑数组。因此如果要访问,需要先将内容复制到堆内存中,再进行访问。 以下示例是复合缓冲区将堆缓冲区和直接缓冲区组合在一起,没有进行任何复制过程,仅仅创建了一个视图而已。 ```java public class ByteBufCompositeBufferDemo { public static void main(String[] args) { // 创建一个堆缓冲区 ByteBuf heapBuf = Unpooled.buffer(3); String way = "way"; heapBuf.writeBytes(way.getBytes()); // 创建一个直接缓冲区 ByteBuf directBuf = Unpooled.directBuffer(3); String lau = "lau"; directBuf.writeBytes(lau.getBytes()); // 创建一个复合缓冲区 CompositeByteBuf compositeBuffer = Unpooled.compositeBuffer(10); compositeBuffer.addComponents(heapBuf, directBuf); // 将缓冲区添加到符合缓冲区 // 检查是否是支撑数组. // 不是支撑数组,则为复合缓冲区 if (!compositeBuffer.hasArray()) { for (ByteBuf buffer : compositeBuffer) { // 计算第一个字节的偏移量 int offset = buffer.readerIndex(); // 可读字节数 int length = buffer.readableBytes(); // 获取字节内容 byte[] array = new byte[length]; buffer.getBytes(offset, array); printBuffer(array, offset, length); } } } //打印出Buffer的信息 private static void printBuffer(byte[] array, int offset, int len) { System.out.println("array:" + array); System.out.println("array->String:" + new String(array)); System.out.println("offset:" + offset); System.out.println("len:" + len); } } ``` **运行结果** ``` array:[B@4d76f3f8 array->String:way offset:0 len:3 array:[B@2d8e6db6 array->String:lau offset:0 len:3 ``` `CompositeByteBuf`是一个虚拟的缓冲区,其用途是将多个缓冲区显示为单个合并缓冲区,类似数据库中的视图。 ### 五、池化与非池化 池化的最大意义在于可以重用 ByteBuf,优点有 * 没有池化,则每次都得创建新的 ByteBuf 实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加 GC 压力 * 有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率 * 高并发时,池化功能更节约内存,减少内存溢出的可能 池化功能是否开启,可以通过下面的系统环境变量来设置 ```java -Dio.netty.allocator.type={unpooled|pooled} ``` * 4.1 以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现 * 4.1 之前,池化功能还不成熟,默认是非池化实现 *附参考文章链接* *https://blog.csdn.net/jiang_wang01/article/details/123238001*
标签:
Netty
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2155.html
上一篇
08.Netty之Handler与Pipeline
下一篇
10.Netty之ByteBuf介绍(二)
取消回复
评论啦~
提交评论
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
标签云
随笔
机器学习
Hbase
Map
Zookeeper
Docker
Livy
Spark SQL
Spark Core
NIO
DataWarehouse
Azkaban
Nacos
nginx
HDFS
Shiro
JavaSE
并发线程
Linux
SpringBoot
Kibana
VUE
前端
Spark RDD
Java工具类
数学
JavaWEB项目搭建
递归
设计模式
CentOS
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞