李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
09.Netty之ByteBuf介绍(一)
Leefs
2022-06-11 PM
1522℃
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
NLP
4
标签云
Python
容器深入研究
RSA加解密
Thymeleaf
哈希表
Kibana
Hive
CentOS
数据结构
VUE
JavaSE
递归
Java
MyBatisX
Spark SQL
二叉树
Spark Streaming
栈
Elasticsearch
序列化和反序列化
机器学习
并发线程
Golang
随笔
JavaWeb
JavaScript
HDFS
FastDFS
微服务
人工智能
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭