1 ByteBuf的数据结构
从上一篇的文章中可以看到,最终发送消息或者接收到消息时,都是一个ByteBuf对象,这一篇我们来探索下这个载体类。
ByteBuf是一个数组结构,可以根据下标进行读写操作,也可以进行重复读写,这个完美的解决了传统Stream不可重复读写的问题。
- 从上图从左到右分别是已废弃区域、可读区域、可写区域以及可扩容区域;已废弃字节区域是已经读过,无效的区域;可读区域是ByteBuf数据的主要区域,这个区域里是未读取的数据,后续读取数据都是从这里读取出来的;可写字节区域是后续写入数据时的区域,接下来需要写入的数据都会写到这个区域来;最后一部分是可扩容的区域,当ByteBufcapacity部分用完后,可继续扩容并且写入到这部分区域中。
- 从readIndex开始是读取指针位置,数据从这部分开始读取,每读取一个字节,readIndex就加1,所以可读容量为,writeIndex - readIndex, 当readIndex == writeIndex时,不可再往下读;
- writeIndex表示当前写数据的位置,每写一个字节,writeIndex就加1,直到writeIndex为capacity,调用isWritable()方法时,返回false
- 另外图中最上面的变量是maxCapacity,是指最大容量,即当writerIndex == capacity时,还可以扩充的容量,直到写入的字节数为maxCapacity的值
ByteBuf实际上是一个抽象类,是载体类的抽象,只是定义了使用的api,下面来学习下一些常用的api
2 ByteBuf获取以及回收
默认获取方式
// 初始化bytebuf 初始容量 8,最大容量 30
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(8, 30);
ByteBufAllocator
// 这里也可以可以使用这两种方法获取ByteBufAllocator
ByteBufAllocator allocator = new UnpooledByteBufAllocator(true);
ByteBufAllocator allocator = new PooledByteBufAllocator(true);
true跟false的区别仅在于buffer(),如果是调用heapBuffer(),那还是堆内存,跟true/false无关
堆内存获取方式
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.heapBuffer();
一般情况下我们采用默认的方式获取ByteBuf,如果要显式的获取堆内内存或者使用堆外内存的分配方式,可使用后面两种方式获取。
由于netty默认使用堆外内存,这部分内存不被JVM所管理,GC时也是无法回收这部分内存的,这部分内存需要进行手动管理,有点类似于C/C++语言中内存使用完毕后由程序员进行回收释放,否则会引发内存泄漏;
ByteBuf使用引用计数方式,当创建一个ByteBuf时,它的引用次数为1,当使用完成时,需要调用它的
release()
方法,每调用一次引用减1,直到引用次数为0,则ByteBuf使用的内存会被回收;
假如需要增加它的引用计数,则调用
retain()
3 容量api
capacity()
表示 ByteBuf 底层占用了多少字节的内存(包括丢弃的字节、可读字节、可写字节)
maxCapacity()
表示 ByteBuf 底层最大能够占用多少字节的内存,当向 ByteBuf 中写数据的时候,如果发现容量不足,则进行扩容,直到扩容到 maxCapacity,超过这个数,就抛异常
readableBytes()
readableBytes() 表示 ByteBuf 当前可读的字节数, 计算方式为:readableBytes = writerIndex - readerIndex,如果writerIndex等于readerIndex,则表示数据已经读取完毕,不可再读
isReadable()
当 readableBytes() 大于0,则返回true,表示可读,否则返回false,表示不能不可读了
writableBytes() 与 maxWritableBytes()
writableBytes()表示当前可写字节数,它的值为 capacity - writerIndex, 当capacity等于writerIndex时,表示不可在写入 ;此时如果maxCapacity > capacity的值时,还可以扩容写入,直到writerIndex的值等于maxCapacity
isWritable()
当writerIndex=capacity时,isWritable() 返回false,但是这时并不代表不可写了,如果maxCapacity>capacity,再次调用write…()写入时,capacity 的值为 maxCapacity,并且isWritable()返回true,直到writerIndex等于maxCapacity。
容量代码实践
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(5, 8);
byteBuf.writeInt(4);
show(byteBuf, "writeInt(4)");
byteBuf.readInt();
show(byteBuf, "readInt()");
byteBuf.writeByte(1);
show(byteBuf, "writeByte(1)");
byteBuf.writeShort(2);
show(byteBuf, "writeShort(2)");
4 读写指针
readerIndex() 与 readerIndex(int)
前者表示获取当前读取的下标,后者表示设置当前读下标
markReaderIndex()
resetReaderIndex()
前者表示标记(保存)读下标,后者为设置当前读下标为markReaderIndex()的值
writerIndex()
writerIndex(int)
markWriterIndex()
resetWriterIndex()
同read的用法
读写下标代码实践
byteBuf.writeShort(2);
byteBuf.markWriterIndex();
show(byteBuf, "writeShort(2)");
byteBuf.writeShort(4);
byteBuf.markWriterIndex();
show(byteBuf, "writeShort(4)");
byteBuf.writeByte(1);
show(byteBuf, "writeByte(1)");
byteBuf.resetWriterIndex();
show(byteBuf, "resetWriterIndex()");
// 读下标方法同上
5 读写api
ByteBuf读写的api常用的有 readByte()、 writeByte(), 此外类似的还有readShort()、writeShort(), readInt()、writeInt(),readLong()、writeLong(), readBoolean()、writeBoolean()等等方法
readBytes(byte[] src)、writeBytes(byte[] dst)
writeBytes() 表示把字节数组 src 里面的数据全部写到 ByteBuf,而 readBytes() 指的是把 ByteBuf 里面的数据全部读取到 dst,这里 dst 字节数组的大小通常等于 readableBytes(),而 src 字节数组大小的长度通常小于等于 writableBytes(),以上方法都会影响读写下标
此外还提供了各种get、set方法
ByteBuf.getXXX()
ByteBuf.setXXX()
getXXX(),setXXX()方法都只是读取或者修改数据,不会修改下标
接下来学习下ByteBuf常用的几个"复制"的方法
slice()、duplicate()、copy()
slice()方法默认会从原始的ByteBuf中截图原始ByteBuf中readableBytes长度的ByteBuf,即"复制"后的ByteBuf的maxCapacity为原始ByteBuf的readableBytes,当然slice()也可以指定截取下标和长度;slice()方法的"复制"流程如下
duplicate()方法会"复制"整个原始ByteBuf的数据,相当于slice(0, byteBuf.maxCapacity()), 并且包括了原始ByteBuf的下标信息;"复制"过程如下
copy()方法就如其名字,会复制原始ByteBuf的所有容量以及原始ByteBuf中readableBytes的数据,不包括指针信息,复制的流程如下
三个方法比较,共同点:
1 三个方法都会从原ByteBuf复制一段内容;
2 三个复制后的ByteBuf下标移动,不会影响ByteBuf的下标;
不同点:
1 slice()和duplicate() 中的数据修改,会影响到原ByteBuf的数据
2 slice() 方法与 duplicate() 方法不会拷贝数据,它们只是通过改变读写指针来改变读写的行为,而最后一个方法 copy() 会直接从原始的 ByteBuf 中拷贝所有的信息,包括读写指针以及底层对应的数据,因此,往 copy() 返回的 ByteBuf 中写数据不会影响到原始的 ByteBuf
3 slice()和duplicate() 与原ByteBuf公用引用计数,而copy是一个新的ByteBuf对象,拥有自己的引用计数,所以在使用copy后一定记得调用release()方法释放内存