ByteBuf 和 ByteBuffer 的区别, ByteBuf 动态扩容源码分析

简介: ByteBuf 和 ByteBuffer 的区别, ByteBuf 动态扩容源码分析

前言

在我们进行数据传输的时候, 往往需要使用到缓冲区, 在 Java NIO 中提供的缓冲区类是ByteBuffer, 在Netty框架中提供的缓冲区类是ByteBuf, 接下来我们就针对这两种缓冲区进行对比讲解

ByteBuffer

关于ByteBuffer我之前专门的写过一篇文章讲解概念, 基本使用和方法等

NIO 下的 ByteBuffer简单学习 - 掘金 (juejin.cn)

缺点

  • 长度固定: 一旦对ByteBuffer进行分配完成, 他的容量不能动态扩展和收缩, 当需要编码的对象大于ByteBuffer的容量时, 会发生索引越界异常
  • API功能相对较少: 对一些高级和实用的特性不支持, 需要自己进行对应的封装
  • 只有一个指针position: 读写的时候需要手动的调用rewind()方法和flip()方法

ByteBuf

NettyByteBuf弥补了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
  • 则直接返回
  • 如果checkBoundstrue 同时 (目标容量小于0 或者 目标容量大于maxCapacity)
  • maxCapacity =  2147483647
  • 目标容量不合理 , 报错
  • 如果上面两个都不是, 则进入 else
  • 获取初始化时设置的容量大小 (我们设置的16)
  • 如果初始容量大于等于目标容量
  • 则进行想加操作
  • 否则, 进行扩容操作( this.alloc().calculateNewCapacity()方法 )
  • 执行capacity(newCapacity)方法

网络异常,图片无法展示
|

动态扩容核心方法

接下来我们进入到AbstractByteBufAllocator类的calculateNewCapacity()方法中, 该方法是ByteBuf动态扩容的核心方法

网络异常,图片无法展示
|

在这个方法中, 首先去判断目标容量是否比最大容量还大, 如果目标容量比最大容量还大的话就抛出异常, 实际上这一步在之前我们也见到过了

然后我们为当前ByteBuf的容量capacity设置了一个阈值threshold = 4194304大概 4M 的样子

然后又采用了一个步进4MB的方式进行扩容操作

网络异常,图片无法展示
|

如果目标大小没有达到阈值的话, 以64为基数, 做倍增的方式进行扩容

网络异常,图片无法展示
|



目录
相关文章
|
8月前
|
索引
2.3 ByteBuffer 常见方法
2.3 ByteBuffer 常见方法
31 0
|
8月前
|
存储
ByteBuffer 大小分配
ByteBuffer 大小分配
53 0
|
10月前
|
存储 Java Linux
Netty ByteBuf 的零拷贝(Zero Copy)详解
Netty ByteBuf 的零拷贝(Zero Copy)详解
125 0
java Nio(二): Buffer(缓冲区)的数据存取
java Nio(二): Buffer(缓冲区)的数据存取
java Nio(二): Buffer(缓冲区)的数据存取
|
Java
|
设计模式 Java 开发者
Netty为什么要手动释放ByteBuf资源?
Netty为什么要手动释放ByteBuf资源?
395 0
|
存储 消息中间件 缓存
ByteBuf 和 ByteBuffer的比对
ByteBuf 和 ByteBuffer的比对
209 0
|
存储 缓存 Java
如何使用 Java 中 缓冲区类 Buffer
# 如何使用 Java 中 缓冲区类 Buffer ## 1. 什么是Buffer 缓冲区 缓冲区(Buffer):就是在内存中预留指定大小的存储空间用来对输入/输出(I/O)的数据作临时存储,这部分预留的内存空间就叫做缓冲区 缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个数组,该对象提供了一组方法,可以更轻松地使用内存块 ## 2.Buffer及其常用子类 从 JDK1.4开始,提供使用Buffer类 ![image-20220502214516083](https://yygh-sz.oss-cn-beijing.aliyuncs.com/image-2022
117 0
|
存储 索引
Netty原理:ByteBuf对Nio bytebuffer做了什么导致效率提升?(2)
Netty原理:ByteBuf对Nio bytebuffer做了什么导致效率提升?(2)
|
存储 Java 索引
Netty原理:ByteBuf对Nio bytebuffer做了什么导致效率提升?(1)
Netty原理:ByteBuf对Nio bytebuffer做了什么导致效率提升?(1)
Netty原理:ByteBuf对Nio bytebuffer做了什么导致效率提升?(1)