【每日知识点精讲】9月2日 Netty 中的数据传输载体 ByteBuf
大家好,我是 Carrey。今天精讲的是 Netty 中很重要的 ByteBuf,它是 Netty 中数据交互的基本单位。
ByteBuf结构解析
光靠文字不好理解,先上图

上面就是 ByteBuf 的结构图了,接下来进入精讲
ByteBuf 是一个字节容器,容器主要分为以下部分
- 已经废弃的字节,这部分是无效数据
- 可读字节,这部分是 ByteBuf 的主体数据,是从 ByteBuf 读数据的部分
- 可写字节,所有新写入ByteBuf的数据都会写到这部分中
- 可扩容部分。图中最后一部分得虚线表示该 ByteBuf 还能扩容的字节数
除了可扩容部分,包含数据的部分是通过两个指针进行划分的
- 读指针 readerIndex 每次从 ByteBuf 中读一个字节,读指针右移一位,此时 ByteBuf 中还有 writerIndex - readerIndex 个字节可读
- 写指针 writerIndex 每次向 ByteBuf 中写一个字节,写指针右移一位,此时 ByteBuf 中还有 capacity- writerIndex 个字节可写
当容量 capacity 等于 writerIndex,ByteBuf 不可写,就会进行扩容。 扩容机制如下:
- 假设 ByteBuf 初始容量是10。 如果写入后数据大小未超过512个字节,则选择下一个16的整数倍进行扩容。 比如写入数据后大小为12,则扩容后的capacity是16。
- 如果写入后数据大小超过512个字节,则选择下一个2的n次方。比如写入后大小是512字节,则扩容后的capacity是2的10次方=1024。(因为2的9次方=512,长度已经不够了)
- 扩容不能超过max capacity,否则会报错。
关于扩容机制的源码解读,可以看这篇:(132条消息) 记录一下netty ByteBuf扩容机制_chenm1xuexi的博客-CSDN博客
总之,ByteBuf 是 Netty 对于二进制数据的抽象,这个数据结构可以有效区分可读数据和可写数据。
接下来介绍 ByteBuf 中一些常见的 API
ByteBuf 常见 API
读写指针相关
- readerIndex() 与 readerIndex(int) 前者表示返回当前的读指针 readerIndex, 后者表示设置读指针
- writeIndex() 与 writeIndex(int) 前者表示返回当前的写指针 writerIndex, 后者表示设置写指针
- markReaderIndex() 与 resetReaderIndex() 前者表示把当前的读指针保存起来,后者表示把当前的读指针恢复到之前保存的值,代码实例如下:
// 保存
buffer.markReaderIndex();
// 恢复
buffer.resetReaderIndex();
- markWriterIndex() 与 resetWriterIndex() 前者表示把当前的写指针保存起来,后者表示把当前的写指针恢复到之前保存的值。
读写相关
- writeBytes(byte[] src) 表示把字节数组 src 里面的数据全部写到 ByteBuf,src 字节数组大小的长度通常小于等于writableBytes()
- readBytes(byte[] dst) writeBytes() 表示把 ByteBuf 里面的数据全部读取到 dst,这里 dst 字节数组的大小通常等于 readableBytes()
- writeByte() 表示往 ByteBuf 中写一个字节
- readByte() 表示从 ByteBuf 中读取一个字节
- writeBoolean()、writeChar()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble() 与 readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble() 等,见名知意
- getBytes、getByte() 与 setBytes()、setByte() 也是读字节和写字节,区别是 get/set 不会改变读写指针,而 read/write 会改变读写指针,在解析数据时要注意。
- release() 与 retain() 前者是让该 ByteBuf 引用计数减1,后者则相反
- 什么是引用计数?Netty中使用了不被 JVM 直接管理的堆外内存,需要手动回收,而 Netty 中 ButeBuf是通过引用计数进行管理的。创建时引用为1,然后每次调用retain方法引用加1,调用release方法则引用计数减1。当引用计数为 0 时,ByteBuf 底层的内存会被直接回收。
- slice()、duplicate()、copy() 切片、副本和拷贝
- slice() 从原始 ByteBuf 中截取一段从 readerIndex 到 writeIndex 的数据,底层内存以及引用计数与原始的 ByteBuf 共享,但维持着与原始 ByteBuf 相同的内存引用计数和不同的读写指针
- duplicate() 把整个 ByteBuf 都截取出来,包括所有的数据,指针信息。底层内存以及引用计数与原始的 ByteBuf 共享,但维持着与原始 ByteBuf 相同的内存引用计数和不同的读写指针
- copy() 会直接从原始的 ByteBuf 中拷贝所有的信息,包括读写指针以及底层对应的数据。所以往 copy() 返回的 ByteBuf 中写数据不会影响到原始的 ByteBuf。
- 这三个方法均维护着自己的读写指针,和原 ByteBuf 之间互不影响。
- retainedSlice() 与 retainedDuplicate() 在截取内存片段的同时,增加内存的引用计数。因为slice() 和 duplicate() 不会改变 ByteBuf 的引用计数,所以他们依赖的 ByteBuf 调用 release() 之后发现引用计数为零,会释放内存,调用这两个方法返回的 ByteBuf 也会被释放,此时再对它们进行读写,就会报错。可能有人总粗心忘记截取片段的同时增加引用计数,就有了 retainedSlice() 与 retainedDuplicate() 这两个方法。
容量相关
以下 API 见名知意,容易理解,只简单介绍
- capacity() 容量,包括废弃的字节、可读字节、可写字节
- maxCapacity() 底层最大能够占用多少字节的内存,扩容超过 maxCapacity就会报错
- readableBytes() 、 isReadable() 可读字节数和是否可读
- writableBytes()、 isWritable() 可写字节数和是否可写
- maxWritableBytes() 可写的最大字节数,等于 maxCapacity-writerIndex
总结
今天主要讲了 Netty 中数据传输单位 ByteBuf 的底层结构和常见API。里面有一些细节如读写双指针的应用、扩容机制、引用计数机制、常见API细节等。希望能对大家有所帮助。
明日精讲知识点预告:Redis的应用(分布式锁、延时队列等)或者归并排序精讲+常见面试题。敬请期待!
评论
问答助学
相关内容
0个评论
全部评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
作者分享
字节跳动内推,火热招聘中~
4
#内推#字节跳动
大家好!我是东华24届校友,也是鱼皮的学弟,目前在字节跳动-抖音研发团队担任工程师。
字节跳动一直以创新和技术驱动为核心,抖音更是在全球范围内产生了巨大的影响力。如今,我们团队已经开启了 2025 年的秋招。
我所在的抖音研发团队,主要负责抖音短视频的业务和技术研发、相关产品的内容安全业务。截至2021年6月,抖音日活用户超6亿。
未来一年,抖音将更多关注对创作者和社会的价值。团队汇聚各种背景的优秀人才,在更大体量更为复杂的业务场景中不断提升。
如果你对字节跳动-抖音研发团队充满好奇和向往,欢迎加入我们,字节内推码:34XM35E,投递链接:https://job.toutiao.com/s/irq6Hx3M
5
阿里评估面(Java 后端)
14
面试官:来,说一说TCP中的滑动窗口
8
面试官:TCP协议的重传机制了解过吗?来讲一讲!
17
