ByteBuf 是 Netty 中的数据交互单位,本质是一个 Byte 数组的缓冲区,有不同实现机制,首先看 ByteBuf 的数据结构
ByteBuf结构
+-------------------+------------------+------------------+
| discardable bytes | readable bytes | writable bytes |
| | (CONTENT) | |
+-------------------+------------------+------------------+
| | | |
0 <= readerIndex <= writerIndex <= capacity
ByteBuf 包括三部分,丢弃字节、可读字节、可写字节
通过两个指针,读指针(readerIndex)和写指针(writerIndex)来分成三部分,当 readerIndex = writerIndex 时不可读,当 writerIndex = capacity 时不可写。还有个参数 maxCapacity,当写入数据容量不足时会自动扩容,扩容的最大容量为 maxCapacity 值
常用API
容量API
capacity()
表示 ByteBuf 的占用字节内存,包括丢弃字节、可读字节、可写字节,不同的底层实现机制有不同的计算方式
maxCapacity()
表示 ByteBuf 最大能够占用多少字节的内存
readableBytes()
ByteBuf 当前可读的字节数,它的值等于 writerIndex-readerIndex
isReadable()
返回是否可读,writerIndex = readerIndex 则不可读,返回
false
writableBytes()
ByteBuf 当前可写的字节数,它的值等于 capacity-writerIndex
isWritable()
返回是否可写,capacity = writerIndex 则不可写
maxWritableBytes()
ByteBuf 可写的最大字节数,它的值等于 maxCapacity-writerIndex
指针相关API
readerIndex()
返回当前读指针的 readerIndex
readerIndex(int)
设置读指针
writeIndex()
返回当前写指针的 writerIndex
writeIndex(int)
设置读指针
markReaderIndex()
、markWriterIndex()
把当前的读、写指针保存起来
resetReaderIndex()
、resetWriterIndex()
把当前的读、写指针恢复到之前保存的值
读写API
writeBytes(byte[] src)
把字节数组 src 里面的数据全部写到 ByteBuf,src 字节数组大小的长度通常小于等于
writableBytes()
readBytes(byte[] dst)
把 ByteBuf 里面的数据全部读取到 dst,这里 dst 字节数组的大小通常等于
readableBytes()
writeByte(byte b)
表示往 ByteBuf 中写一个字节,类似还有
writeBoolean()
、writeChar()
、writeShort()
、writeInt()
、writeLong()
、writeFloat()
、writeDouble()
readByte()
表示从 ByteBuf 中读取一个字节,类似还有
readBoolean()
、readChar()
、readShort()
、readInt()
、readLong()
、readFloat()
、readDouble()
setBytes()
、setByte()
和
writeBytes()
等方法类似,但是 set 不会改变读写指针,而 write 会改变写指针getBytes
、getByte()
和
readBytes()
等方法类似,同样 get 不会改变读写指针,而 read 会改变读指针retain()
将 BetyBuf 的引用计数加一
release()
将 ByteBuf 的引用计数减一,减完之后如果发现引用计数为0,则直接回收 ByteBuf 底层的内存
由于 Netty 使用了堆外内存,而堆外内存是不被 jvm 直接管理的,也就是说申请到的内存无法被垃圾回收器直接回收,所以需要我们手动回收。有点类似于c语言里面,申请到的内存必须手工释放,否则会造成内存泄漏。
Netty 的 ByteBuf 是通过引用计数的方式管理的,如果一个 ByteBuf 没有地方被引用到,需要回收底层内存。默认情况下,当创建完一个 ByteBuf,它的引用为1
slice()
从原始 ByteBuf 中截取一段,这段数据是从 readerIndex 到 writeIndex,同时,返回的新的 ByteBuf 的最大容量 maxCapacity 为原始 ByteBuf 的
readableBytes()
;底层内存以及引用计数与原始的 ByteBuf 共享duplicate()
把整个 ByteBuf 都截取出来,包括所有的指针信息。底层内存以及引用计数与原始的 ByteBuf 共享
copy()
从原始的 ByteBuf 中拷贝所有的信息,包括读写指针以及底层对应的数据,底层内存以及引用计数都独立,操作 ByteBuf 中的数据不会影响到原始的 ByteBuf
三个方法都会返回新的 ByteBuf 对象
slice()
与duplicate()
的相同点是:底层内存以及引用计数与原始的 ByteBuf 共享,也就是说经过slice()
或者duplicate()
返回的 ByteBuf 调用 write 系列方法都会影响到原始的 ByteBuf,但是它们都维持着与原始 ByteBuf 相同的内存引用计数和不同的读写指针
slice()
与duplicate()
的不同点是:slice()
只截取从 readerIndex 到 writerIndex 之间的数据,它返回的 ByteBuf 的最大容量被限制到 原始 ByteBuf 的readableBytes()
, 而duplicate()
是把整个 ByteBuf 都与原始的 ByteBuf 共享
slice()
和duplicate()
不会改变 ByteBuf 的引用计数,所以原始的 ByteBuf 调用release()
之后发现引用计数为零,就开始释放内存,调用这两个方法返回的 ByteBuf 也会被释放,这个时候如果再对它们进行读写,就会报错。因此,我们可以通过调用一次retain()
方法 来增加引用,表示它们对应的底层的内存多了一次引用,引用计数为2,在释放内存的时候,需要调用两次release()
方法,将引用计数降到零,才会释放内存
slice()
、duplicate()
、copy()
三个方法均维护着自己的读写指针,与原始的 ByteBuf 的读写指针无关,相互之间不受影响
retainedSlice()
截取内存片段的同时,增加内存的引用计数,等价于
slice().retain()
retainedDuplicate()
也是截取内存片段的同时,增加内存的引用计数,等价于
duplicate().retain()