一文搞懂ByteBuf的基础使用

前言

NIO 中的ByteBuffer 使用起来多有不便,读写需要切换容量固定 等问题让ByteBuffer 易用性急剧降低,因此在Netty 中设计了ByteBuf 来代替ByteBuffer进行使用。

本文将通过图示和简单示例,快速对ByteBuf 进行讲解,使用的Netty 版本为4.1.6.Final

正文

一. ByteBuf结构

在初始创建一个ByteBuf 出来时,ByteBuf如下所示。

ByteBuf 有两个关键指针,writerIndex 表示写指针,永远指向下一次写入的开始位置,readerIndex 表示读指针,永远指向下一次读取的开始位置。通过上图还可以知道,ByteBuf 的初始容量是256,最大容量是Integer.MAX_VALUE,即整形最大值。

此时往ByteBuf 写入四个字节,写入字节后的ByteBuf如下所示。

写入四个字节后,writerIndex 向后移动了四位来到了索引为4的位置,此时索引范围[readerIndex , writerIndex )上的字节全部为可读字节,因此如果writerIndex 等于writerIndex ,那么实际上是没有可读字节的,此时如果还通过ByteBuf 读取字节,就会抛出IndexOutOfBoundsException

现在通过ByteBuf 读取两个字节,读取字节后的ByteBuf如下所示。

读取过的字节就变成可废弃字节,此时调用ByteBufdiscardReadyBytes() 方法可以把可废弃字节的空间回收掉,就像下面这样。

调用ByteBufdiscardReadyBytes() 方法后,假如回收了N 字节的空间,那么读写指针就要往前移动N 个位置,相应的可读字节的位置也要往前移动N 个位置,这里的问题就在于可读字节移动位置时会发生内存复制,所以尽管ByteBufdiscardReadyBytes() 方法会释放内存空间,但是也会造成内存复制,那么频繁调用discardReadyBytes() 方法就会影响效率。

ByteBuf 还提供了若干操作读写指针的方法,使得ByteBuf数据的读写十分灵活方便,常用的方法说明如下。

  • markWriterIndex() 。将当前writerIndex 的值赋值给markedWriterIndex ,其中markedWriterIndex初始默认为0;
  • markReaderIndex() 。将当前readerIndex 的值赋值给markedReaderIndex ,其中markedReaderIndex初始默认为0;
  • resetWriterIndex() 。将当前writerIndex 重置为markedWriterIndex
  • resetReaderIndex() 。将当前readerIndex 重置为markedReaderIndex
  • writerIndex(int writerIndex) 。将当前writerIndex设置为指定值;
  • readerIndex(int readerIndex) 。将当前readerIndex设置为指定值;
  • clear() 。将当前writerIndexreaderIndex重置为0。

上述这些操作读写指针的方法,仅改变了读写指针的位置,不会影响到实际内存里面的数据,所以像可废弃字节这种读取过的数据,可以通过操作读指针,重复进行读取。但是有一点需要注意,可读字节的索引范围一定是[readerIndex , writerIndex ),那么在操作读写指针时,不能让readerIndex 大于等于writerIndex 时还去进行读操作,否则会报IndexOutOfBoundsException异常。

二. ByteBuf使用模式

ByteBuf 有三种使用模式,分别是堆缓冲区直接缓冲区复合缓冲区 ,先抛开复合缓冲区不谈,堆缓冲区就是数据分配在JVM 的堆空间中,而直接缓冲区的数据是分配在JVM 管控之外的堆空间中,那么使用堆缓冲区的好处就是分配时更快且安全,但坏处就是在进行IO操作时会先将数据从堆缓冲区拷贝到直接缓冲区,相较于直接使用直接缓冲区来说,多了一次拷贝操作,具体可以参考下面图示。

三. ByteBuf扩容

ByteBuf 相较于ByteBuffer ,显著的一点区别在于ByteBuf 支持动态扩容,当写入数据时,ByetBuf 会根据本次写入数据长度(minWritableBytes )加上写指针所在索引值(writerIndex ),得到一个最小需要的容量值(minNewCapacity ),然后根据minNewCapacity的不同,会有不同的扩容行为,具体行为归纳如下。

  1. minNewCapacity 如果等于4MB 则扩容后容量为4MB
  2. minNewCapacity 如果小于4MB 则扩容后容量为大于等于minNewCapacity 的最小的64 乘以2的幂;
  3. minNewCapacity 如果大于4MB 则扩容后容量为(minNewCapacity /4MB + 1 ) * 4MB
  4. 扩容后的容量最大不能超过Integer.MAX_VALUE

总结

关于ByteBuf 的基础使用,其实知道ByteBuf 的读写指针的一个读取和写入规则,以及如何扩容就够了,其它的使用方式例如视图和释放等,在Netty实战时用到再分析。

相关推荐
IT书架几秒前
golang面试题
开发语言·后端·golang
机器之心1 小时前
全球十亿级轨迹点驱动,首个轨迹基础大模型来了
人工智能·后端
潜洋2 小时前
Spring Boot教程之五:在 IntelliJ IDEA 中运行第一个 Spring Boot 应用程序
java·spring boot·后端
St_Ludwig3 小时前
C语言 蓝桥杯某例题解决方案(查找完数)
c语言·c++·后端·算法·游戏·蓝桥杯
vener_3 小时前
LuckySheet协同编辑后端示例(Django+Channel,Websocket通信)
javascript·后端·python·websocket·django·luckysheet
计算机毕设孵化场3 小时前
计算机毕设-基于springboot的多彩吉安红色旅游网站的设计与实现(附源码+lw+ppt+开题报告)
vue.js·spring boot·后端·计算机外设·课程设计·计算机毕设论文·多彩吉安红色旅游网站
爪哇学长3 小时前
解锁API的无限潜力:RESTful、SOAP、GraphQL和Webhooks的应用前景
java·开发语言·后端·restful·graphql
gma9994 小时前
brpc 与 Etcd 二次封装
数据库·c++·rpc·etcd
战神刘玉栋4 小时前
《SpringBoot、Vue 组装exe与套壳保姆级教学》
vue.js·spring boot·后端
码到成功>_<5 小时前
Spring Boot实现License生成和校验
数据库·spring boot·后端