一文搞懂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实战时用到再分析。

相关推荐
凯雀安全2 小时前
printspoofer的RPC调用接口的简单代码
qt·网络协议·rpc
全栈派森2 小时前
云存储最佳实践
后端·python·程序人生·flask
CircleMouse2 小时前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
獨枭3 小时前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端
维基框架3 小时前
Spring Boot 封装 MinIO 工具
java·spring boot·后端
秋野酱3 小时前
基于javaweb的SpringBoot酒店管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
☞无能盖世♛逞何英雄☜4 小时前
Flask框架搭建
后端·python·flask
进击的雷神4 小时前
Perl语言深度考查:从文本处理到正则表达式的全面掌握
开发语言·后端·scala
进击的雷神4 小时前
Perl测试起步:从零到精通的完整指南
开发语言·后端·scala
豌豆花下猫5 小时前
Python 潮流周刊#102:微软裁员 Faster CPython 团队(摘要)
后端·python·ai