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 则不可读,返回
falsewritableBytes()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()