【每日知识点精讲】9月2日 Netty 中的数据传输载体 ByteBuf

大家好,我是 Carrey。今天精讲的是 Netty 中很重要的 ByteBuf,它是 Netty 中数据交互的基本单位。


ByteBuf结构解析

光靠文字不好理解,先上图



上面就是 ByteBuf 的结构图了,接下来进入精讲


ByteBuf 是一个字节容器,容器主要分为以下部分

  1. 已经废弃的字节,这部分是无效数据
  2. 可读字节,这部分是 ByteBuf 的主体数据,是从 ByteBuf 读数据的部分
  3. 可写字节,所有新写入ByteBuf的数据都会写到这部分中
  4. 可扩容部分。图中最后一部分得虚线表示该 ByteBuf 还能扩容的字节数

除了可扩容部分,包含数据的部分是通过两个指针进行划分的

  1. 读指针 readerIndex 每次从 ByteBuf 中读一个字节,读指针右移一位,此时 ByteBuf 中还有 writerIndex - readerIndex 个字节可读
  2. 写指针 writerIndex 每次向 ByteBuf 中写一个字节,写指针右移一位,此时 ByteBuf 中还有 capacity- writerIndex 个字节可写

当容量 capacity 等于 writerIndex,ByteBuf 不可写,就会进行扩容。 扩容机制如下:

  1. 假设 ByteBuf 初始容量是10。 如果写入后数据大小未超过512个字节,则选择下一个16的整数倍进行扩容。 比如写入数据后大小为12,则扩容后的capacity是16。
  2. 如果写入后数据大小超过512个字节,则选择下一个2的n次方。比如写入后大小是512字节,则扩容后的capacity是2的10次方=1024。(因为2的9次方=512,长度已经不够了)
  3. 扩容不能超过max capacity,否则会报错。

关于扩容机制的源码解读,可以看这篇:(132条消息) 记录一下netty ByteBuf扩容机制_chenm1xuexi的博客-CSDN博客

总之,ByteBuf 是 Netty 对于二进制数据的抽象,这个数据结构可以有效区分可读数据和可写数据。


接下来介绍 ByteBuf 中一些常见的 API

ByteBuf 常见 API

读写指针相关

  1. readerIndex() 与 readerIndex(int) 前者表示返回当前的读指针 readerIndex, 后者表示设置读指针
  2. writeIndex() 与 writeIndex(int) 前者表示返回当前的写指针 writerIndex, 后者表示设置写指针
  3. markReaderIndex() 与 resetReaderIndex() 前者表示把当前的读指针保存起来,后者表示把当前的读指针恢复到之前保存的值,代码实例如下:
// 保存
buffer.markReaderIndex();
// 恢复
buffer.resetReaderIndex();
  1. markWriterIndex() 与 resetWriterIndex() 前者表示把当前的写指针保存起来,后者表示把当前的写指针恢复到之前保存的值。


读写相关
  1. writeBytes(byte[] src) 表示把字节数组 src 里面的数据全部写到 ByteBuf,src 字节数组大小的长度通常小于等于writableBytes()
  2. readBytes(byte[] dst) writeBytes() 表示把 ByteBuf 里面的数据全部读取到 dst,这里 dst 字节数组的大小通常等于 readableBytes()
  3. writeByte() 表示往 ByteBuf 中写一个字节
  4. readByte() 表示从 ByteBuf 中读取一个字节
  5. writeBoolean()、writeChar()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble() 与 readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble() 等,见名知意
  6. getBytes、getByte() 与 setBytes()、setByte() 也是读字节和写字节,区别是 get/set 不会改变读写指针,而 read/write 会改变读写指针,在解析数据时要注意。
  7. release() 与 retain() 前者是让该 ByteBuf 引用计数减1,后者则相反
  8. 什么是引用计数?Netty中使用了不被 JVM 直接管理的堆外内存,需要手动回收,而 Netty 中 ButeBuf是通过引用计数进行管理的。创建时引用为1,然后每次调用retain方法引用加1,调用release方法则引用计数减1。当引用计数为 0 时,ByteBuf 底层的内存会被直接回收。
  9. slice()、duplicate()、copy() 切片、副本和拷贝
  10. slice() 从原始 ByteBuf 中截取一段从 readerIndex 到 writeIndex 的数据,底层内存以及引用计数与原始的 ByteBuf 共享,但维持着与原始 ByteBuf 相同的内存引用计数和不同的读写指针
  11. duplicate() 把整个 ByteBuf 都截取出来,包括所有的数据,指针信息。底层内存以及引用计数与原始的 ByteBuf 共享,但维持着与原始 ByteBuf 相同的内存引用计数和不同的读写指针
  12. copy() 会直接从原始的 ByteBuf 中拷贝所有的信息,包括读写指针以及底层对应的数据。所以往 copy() 返回的 ByteBuf 中写数据不会影响到原始的 ByteBuf。
  13. 这三个方法均维护着自己的读写指针,和原 ByteBuf 之间互不影响。
  14. retainedSlice() 与 retainedDuplicate() 在截取内存片段的同时,增加内存的引用计数。因为slice() 和 duplicate() 不会改变 ByteBuf 的引用计数,所以他们依赖的 ByteBuf 调用 release() 之后发现引用计数为零,会释放内存,调用这两个方法返回的 ByteBuf 也会被释放,此时再对它们进行读写,就会报错。可能有人总粗心忘记截取片段的同时增加引用计数,就有了 retainedSlice() 与 retainedDuplicate() 这两个方法。


容量相关

以下 API 见名知意,容易理解,只简单介绍

  1. capacity() 容量,包括废弃的字节、可读字节、可写字节
  2. maxCapacity() 底层最大能够占用多少字节的内存,扩容超过 maxCapacity就会报错
  3. readableBytes() 、 isReadable() 可读字节数和是否可读
  4. writableBytes()、 isWritable() 可写字节数和是否可写
  5. maxWritableBytes() 可写的最大字节数,等于 maxCapacity-writerIndex


总结

今天主要讲了 Netty 中数据传输单位 ByteBuf 的底层结构和常见API。里面有一些细节如读写双指针的应用、扩容机制、引用计数机制、常见API细节等。希望能对大家有所帮助。


明日精讲知识点预告:Redis的应用(分布式锁、延时队列等)或者归并排序精讲+常见面试题。敬请期待!


0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
CarreyWu
下载 APP