前言
在我们进行数据传输的时候, 往往需要使用到缓冲区, 在 Java NIO 中提供的缓冲区类是ByteBuffer
, 在Netty
框架中提供的缓冲区类是ByteBuf
, 接下来我们就针对这两种缓冲区进行对比讲解
ByteBuffer
关于ByteBuffer
我之前专门的写过一篇文章讲解概念, 基本使用和方法等
NIO 下的 ByteBuffer简单学习 - 掘金 (juejin.cn)
缺点
- 长度固定: 一旦对
ByteBuffer
进行分配完成, 他的容量不能动态扩展和收缩, 当需要编码的对象大于ByteBuffer
的容量时, 会发生索引越界异常 - API功能相对较少: 对一些高级和实用的特性不支持, 需要自己进行对应的封装
- 只有一个指针
position
: 读写的时候需要手动的调用rewind()
方法和flip()
方法
ByteBuf
Netty
的ByteBuf
弥补了ByteBuffer
的不足之处, 两者是在同等层面, 都是用于缓冲区的
优势
- 支持池化: 更节约内存, 减少内存溢出的可能
- 读写指针分离, 不需要像
ByteBuffer
一样需要调用方法来切换读写模式 - 可以自动扩容, 有效解决了
ByteBuffer
的索引越界异常 - 支持方法的链式调用
- 很多地方体现了零拷贝
动态扩容机制
代码体验一下自动扩容机制
工具类
public class ByteBufUtil { public static void log(ByteBuf buf){ final int length = buf.readableBytes(); int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4; StringBuilder str = new StringBuilder(rows * 80 * 2) .append("read index:").append(buf.readerIndex()) .append(" write index: ").append(buf.writerIndex()) .append(" capacity:").append(buf.capacity()); appendPrettyHexDump(str, buf); System.out.println(str.toString()); } } 复制代码
启动类
public static void main(String[] args) { // 创建 ByteBuf, 初始化长度为 16 ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(16); ByteBufUtil.log(byteBuf); // 向 byteBuf 缓冲区写入数据 StringBuilder str = new StringBuilder(); for (int i = 0; i < 10; i++) { str.append("nx"); } byteBuf.writeBytes(str.toString().getBytes()); // 打印当前 byteBug ByteBufUtil.log(byteBuf); } 复制代码
打印结果
根据我们的启动类代码可以看到, 我们的ByteBuf
初始长度只有16
, 后面我们为其添加了20字节的元素, ByteBuf
的容量被自动的扩容到了64
判断是否动态扩容
首先我们在写入方法ByteBuf.writeBytes()
处打断点, 通过debug
模式进入该方法中
然后我们会进入到AbstractByteBuf
类的writeBytes()
方法, 方法重写, 进入上面那个真正的方法
在该方法中, 第912行, this.ensureWritable()
方法是对入参长度和当前ByteBuf
容量进行扩容的方法, 我们点进去看一下详情
然后会进入下面的这个方法, 我主要讲解一下ensureWritable0()
方法的流程:
- 获取当前的写入下标
- 获取目标容量
- 如果目标容量大于等于0 同时目标容量比
capacity
小
- 则直接返回
- 如果
checkBounds
为true
同时 (目标容量小于0 或者 目标容量大于maxCapacity
)
- maxCapacity = 2147483647
- 目标容量不合理 , 报错
- 如果上面两个都不是, 则进入 else
- 获取初始化时设置的容量大小 (我们设置的16)
- 如果初始容量大于等于目标容量
- 则进行想加操作
- 否则, 进行扩容操作(
this.alloc().calculateNewCapacity()
方法 ) - 执行
capacity(newCapacity)
方法
动态扩容核心方法
接下来我们进入到AbstractByteBufAllocator
类的calculateNewCapacity()
方法中, 该方法是ByteBuf
动态扩容的核心方法
在这个方法中, 首先去判断目标容量是否比最大容量还大, 如果目标容量比最大容量还大的话就抛出异常, 实际上这一步在之前我们也见到过了
然后我们为当前ByteBuf
的容量capacity
设置了一个阈值threshold = 4194304
大概 4M 的样子
然后又采用了一个步进4MB
的方式进行扩容操作
如果目标大小没有达到阈值的话, 以64
为基数, 做倍增的方式进行扩容