netty(六)ByteBuf详解
一、结构图解
ByteBuf
字节容器,用于储存读写数据,且读写之间互相没有冲突,是Netty操作数据的唯一指定数据结构。
废弃字节
Byte中废弃的字节,简单理解为已经被读取过的字节,可以通过discardReadBytes()方法进行丢弃,并释放这部分空间。
可读字节
顾名思义,可以被读取的字节空间,由读指针和写指针进行划分,两个指针中间的字节空间即为可以被读取的字节大小。
计算:可读字节 = WriteIndex - ReadIndex
PS:由上面公式可知,当WriteIndex等于ReadIndex时,ByteBuf不可读。
可写字节
顾名思义,可以被写入的字节空间,每写入一个字节,WriteIndex+1,直到WriteIndex等于容量Capacity时,ByteBuf不可写。
当写入数据容量不足时,会进行扩容,直到扩容至MaxCapacity,具体实现后面再进行分析。
二、代码解析
说再多也不如写出代码跑一遍来得印象深刻,示例代码如下:
PS:以下代码小伙伴们最好自己动手敲一遍,记忆深刻,如果只是复制了跑一遍,我认为理解可能不一定能那么到位。
package byteBufDemo; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import java.nio.charset.StandardCharsets; /** * ByteBuf使用API * * @author 有梦想的肥宅 * @date 2022/6/3 */ public class ByteBufDemo { public static void main(String[] args) throws InterruptedException { //1、把消息内容通过Netty自带的缓存工具类转换成ByteBuf对象 byte[] msg = "【有梦想的肥宅】".getBytes(StandardCharsets.UTF_8); ByteBuf byteBuf = ByteBufAllocator.DEFAULT.heapBuffer(msg.length); byteBuf.writeBytes(msg); //2、操作容量相关API System.out.println("==========A、开始操作容量相关的API=========="); System.out.println("1、输出ByteBuf容量capacity:" + byteBuf.capacity()); System.out.println("2、输出ByteBuf最大容量maxCapacity:" + byteBuf.maxCapacity()); System.out.println("3、输出ByteBuf当前可读字节数readableBytes:" + byteBuf.readableBytes()); System.out.println("4、输出ByteBuf当前是否可读isReadable:" + byteBuf.isReadable()); System.out.println("5、输出ByteBuf当前可写字节数writableBytes:" + byteBuf.writableBytes()); System.out.println("6、输出ByteBuf当前是否可写isWritable:" + byteBuf.isWritable()); System.out.println("7、输出ByteBuf可写的最大字节数maxWritableBytes:" + byteBuf.maxWritableBytes()); System.out.println(); //3、操作读写指针相关API System.out.println("==========B、开始操作读写指针相关API=========="); System.out.println("1、输出ByteBuf读指针readerIndex:" + byteBuf.readerIndex()); System.out.println("2、输出ByteBuf写指针writerIndex:" + byteBuf.writerIndex()); System.out.println("3、开始调用markReaderIndex()方法保存读指针:" + byteBuf.markReaderIndex()); System.out.println("4、开始调用resetReaderIndex()方法恢复读指针【实现重复读】:" + byteBuf.resetReaderIndex()); System.out.println("5、开始调用markWriterIndex()方法保存写指针:" + byteBuf.markWriterIndex()); System.out.println("6、开始调用resetWriterIndex()方法恢复写指针【实现重复写】:" + byteBuf.resetWriterIndex()); System.out.println(); //4、操作读写相关API System.out.println("==========C、开始操作读写相关API=========="); System.out.println("1、开始调用writeBytes()方法写入数据:" + byteBuf.writeBytes("开始写入消息".getBytes(StandardCharsets.UTF_8))); byte[] readBytes = new byte[byteBuf.readableBytes()];//新建一个容量为byteBuf可读长度的字节数组 byteBuf.readBytes(readBytes);//从ByteBuf中读取数据 System.out.println("2、开始调用readBytes()方法从ByteBuf中读出数据:" + new String(readBytes, StandardCharsets.UTF_8)); //PS:Netty使用了堆外内存,而堆外内存不能被JVM的垃圾回收器回收,所以需要我们手动回收【手动释放内存】 //PS:ByteBuf是通过引用计数的方式管理的,所以需要调用release()方法把引用计数设置为0,才能直接回收内存 System.out.println("3、开始调用retain()方法增加引用计数:" + byteBuf.retain()); System.out.println("4、开始多次调用release()方法直至内存释放:"); System.out.println(" 4.1 释放引用前:byteBuf的状态:" + byteBuf); System.out.println(" 4.2 当前引用计数:" + byteBuf.refCnt()); System.out.println(" 4.3 开始释放引用计数:"); int i = 1; while (byteBuf.refCnt() > 0) { System.out.println(" 第" + i + "次释放引用次数结果:" + byteBuf.release()); i++; } System.out.println(" 4.4 释放引用后:byteBuf的状态:" + byteBuf); System.out.println(); //5、操作复制相关API System.out.println("==========D、开始操作复制相关API=========="); byte[] msgN = "快乐肥肥".getBytes(StandardCharsets.UTF_8); ByteBuf byteBufN = ByteBufAllocator.DEFAULT.heapBuffer(msgN.length); byteBufN.writeBytes(msgN); System.out.println("1、输出原对象:" + byteBufN); ByteBuf slice = byteBufN.slice(); System.out.println("2、调用slice()方法复制对象:" + slice); System.out.println(" 2.1 调用slice()方法有以下特点:"); System.out.println(" 2.1.1 最大容量为原byteBuf的可读容量【新对象的maxCapacity = 原对象的readableBytes()】"); System.out.println(" 2.1.2 底层内存和引用计数与原始的byteBuf共享,但读写指针不同"); System.out.println(" 2.1.3 不复制数据,只通过改变读写指针来改变读写行为"); System.out.println(" 2.1.4 不改变原byteBuf的引用计数,当原byteBuf调用release()方法时,slice()出来的对象也会被释放"); ByteBuf duplicate = byteBufN.duplicate(); System.out.println("3、调用duplicate()方法复制对象:" + duplicate); System.out.println(" 3.1 调用duplicate()方法有以下特点:"); System.out.println(" 3.1.1 最大容量、数据内容、指针位置都和原来的byteBuf一样【整个新的byteBuf都和原byteBuf共享】"); System.out.println(" 3.1.2 底层内存和引用计数与原始的byteBuf共享,但读写指针不同"); System.out.println(" 3.1.3 不复制数据,只通过改变读写指针来改变读写行为"); System.out.println(" 3.1.4 不改变原byteBuf的引用计数,当原byteBuf调用release()方法时,slice()出来的对象也会被释放"); ByteBuf copy = byteBufN.copy(); System.out.println("4、调用copy()方法复制对象:" + copy); System.out.println(" 4.1 调用copy()方法有以下特点:"); System.out.println(" 4.1.1 直接复制一个新的对象出来,包括指针位置、底层对应的数据等【往copy()方法复制出来的对象内写数据,不影响原来的byteBuf】"); System.out.println(" 4.1.2 当原byteBuf调用release()方法时,copy()出来的对象不会被释放"); byteBufN.release(); System.out.println("5、release()方法后,其余对象状态如下:"); System.out.println(" 5.1 原对象:" + byteBufN); System.out.println(" 5.2 slice()方法复制的对象:" + slice); System.out.println(" 5.3 duplicate()方法复制的对象:" + duplicate); System.out.println(" 5.4 copy()方法复制的对象:" + copy); } }
运行结果如下:
三、本章小结
- 1、Netty对二进制数据抽象的结构为ByteBuf类,本质就是引用了一段内存【堆内或堆外内存均可】。
- 2、这段内存通过引用计数来控制是否需要被释放。
- 3、使用读写指针来控制ByteBuf的读写。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2020-06-04 B+树