「我正在参与掘金会员专属活动-源码共读第一期,点击参与」
前言
在我们进行数据传输的时候, 往往需要使用到缓冲区, 在 Java NIO 中提供的缓冲区类是ByteBuffer
, 在Netty
框架中提供的缓冲区类是ByteBuf
, 接下来我们就针对这两种缓冲区进行对比讲解
往期文章
- Netty源码分析(一) backlog 参数 - 掘金 (juejin.cn)
- Netty服务端初始化详解 - 掘金 (juejin.cn)
- Netty服务端启动流程分析 - 掘金 (juejin.cn)
- Netty之第一次 TCP 连接时发生了什么 - 掘金 (juejin.cn)
- Netty之服务启动且注册成功之后 - 掘金 (juejin.cn)
- Netty之服务端channel的初始化 - 掘金 (juejin.cn)
- Netty「源码阅读」之 EventLoop 简单介绍到源码分析 - 掘金 (juejin.cn)
- Netty「源码阅读」之怎么解决 Java 的 epoll 空轮询 bug - 掘金 (juejin.cn)
- 什么是零拷贝, 从 Java 到 Netty - 掘金 (juejin.cn)
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
为基数, 做倍增的方式进行扩容
本文内容到此结束了
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位大佬指出。
我是 宁轩 , 我们下次再见