大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈
前言
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如下所示。
读取过的字节就变成可废弃字节,此时调用ByteBuf的discardReadyBytes() 方法可以把可废弃字节的空间回收掉,就像下面这样。
调用ByteBuf的discardReadyBytes() 方法后,假如回收了N字节的空间,那么读写指针就要往前移动N个位置,相应的可读字节的位置也要往前移动N个位置,这里的问题就在于可读字节移动位置时会发生内存复制,所以尽管ByteBuf的discardReadyBytes() 方法会释放内存空间,但是也会造成内存复制,那么频繁调用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()。将当前writerIndex和readerIndex重置为0。
上述这些操作读写指针的方法,仅改变了读写指针的位置,不会影响到实际内存里面的数据,所以像可废弃字节这种读取过的数据,可以通过操作读指针,重复进行读取。但是有一点需要注意,可读字节的索引范围一定是[readerIndex, writerIndex),那么在操作读写指针时,不能让readerIndex大于等于writerIndex时还去进行读操作,否则会报IndexOutOfBoundsException异常。
二. ByteBuf使用模式
ByteBuf有三种使用模式,分别是堆缓冲区,直接缓冲区和复合缓冲区,先抛开复合缓冲区不谈,堆缓冲区就是数据分配在JVM的堆空间中,而直接缓冲区的数据是分配在JVM管控之外的堆空间中,那么使用堆缓冲区的好处就是分配时更快且安全,但坏处就是在进行IO操作时会先将数据从堆缓冲区拷贝到直接缓冲区,相较于直接使用直接缓冲区来说,多了一次拷贝操作,具体可以参考下面图示。
三. ByteBuf扩容
ByteBuf相较于ByteBuffer,显著的一点区别在于ByteBuf支持动态扩容,当写入数据时,ByetBuf会根据本次写入数据长度(minWritableBytes)加上写指针所在索引值(writerIndex),得到一个最小需要的容量值(minNewCapacity),然后根据minNewCapacity的不同,会有不同的扩容行为,具体行为归纳如下。
- minNewCapacity如果等于4MB则扩容后容量为4MB;
- minNewCapacity如果小于4MB则扩容后容量为大于等于minNewCapacity的最小的64乘以2的幂;
- minNewCapacity如果大于4MB则扩容后容量为(minNewCapacity/4MB + 1) * 4MB;
- 扩容后的容量最大不能超过Integer.MAX_VALUE。
总结
关于ByteBuf的基础使用,其实知道ByteBuf的读写指针的一个读取和写入规则,以及如何扩容就够了,其它的使用方式例如视图和释放等,在Netty实战时用到再分析。
大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实,最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈