首先,咱们先来了解一下 ByteBuf 的结构数组
以上就是一个 ByteBuf 的结构图,从上面这幅图能够看到:微信
Netty 使用 ByteBuf 这个数据结构能够有效地区分可读数据和可写数据,读写之间相互没有冲突,固然,ByteBuf 只是对二进制数据的抽象,具体底层的实现咱们在下面的小节会讲到,在这一小节,咱们 只须要知道 Netty 关于数据读写只认 ByteBuf,下面,咱们就来学习一下 ByteBuf 经常使用的 API数据结构
capacity()jvm
表示 ByteBuf 底层占用了多少字节的内存(包括丢弃的字节、可读字节、可写字节),不一样的底层实现机制有不一样的计算方式,后面咱们讲 ByteBuf 的分类的时候会讲到函数
maxCapacity()学习
表示 ByteBuf 底层最大可以占用多少字节的内存,当向 ByteBuf 中写数据的时候,若是发现容量不足,则进行扩容,直到扩容到 maxCapacity,超过这个数,就抛异常指针
readableBytes() 与 isReadable()code
readableBytes() 表示 ByteBuf 当前可读的字节数,它的值等于 writerIndex-readerIndex,若是二者相等,则不可读,isReadable() 方法返回 false对象
writableBytes()、 isWritable() 与 maxWritableBytes()blog
writableBytes() 表示 ByteBuf 当前可写的字节数,它的值等于 capacity-writerIndex,若是二者相等,则表示不可写,isWritable() 返回 false,可是这个时候,并不表明不能往 ByteBuf 中写数据了, 若是发现往 ByteBuf 中写数据写不进去的话,Netty 会自动扩容 ByteBuf,直到扩容到底层的内存大小为 maxCapacity,而 maxWritableBytes() 就表示可写的最大字节数,它的值等于 maxCapacity-writerIndex
readerIndex() 与 readerIndex(int)
前者表示返回当前的读指针 readerIndex, 后者表示设置读指针
writeIndex() 与 writeIndex(int)
前者表示返回当前的写指针 writerIndex, 后者表示设置写指针
markReaderIndex() 与 resetReaderIndex()
前者表示把当前的读指针保存起来,后者表示把当前的读指针恢复到以前保存的值,下面两段代码是等价的
// 代码片断1 int readerIndex = buffer.readerIndex(); // .. 其余操做 buffer.readerIndex(readerIndex); // 代码片断二 buffer.markReaderIndex(); // .. 其余操做 buffer.resetReaderIndex();
但愿你们多多使用代码片断二这种方式,不须要本身定义变量,不管 buffer 看成参数传递到哪里,调用 resetReaderIndex() 均可以恢复到以前的状态,在解析自定义协议的数据包的时候很是常见,推荐你们使用这一对 API
markWriterIndex() 与 resetWriterIndex()
这一对 API 的做用与上述一对 API 相似,这里再也不 赘述
本质上,关于 ByteBuf 的读写均可以看做从指针开始的地方开始读写数据
writeBytes(byte[] src) 与 buffer.readBytes(byte[] dst)
writeBytes() 表示把字节数组 src 里面的数据所有写到 ByteBuf,而 readBytes() 指的是把 ByteBuf 里面的数据所有读取到 dst,这里 dst 字节数组的大小一般等于 readableBytes(),而 src 字节数组大小的长度一般小于等于 writableBytes()
writeByte(byte b) 与 buffer.readByte()
writeByte() 表示往 ByteBuf 中写一个字节,而 buffer.readByte() 表示从 ByteBuf 中读取一个字节,相似的 API 还有 writeBoolean()、writeChar()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble() 与 readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble() 这里就不一一赘述了,相信读者应该很容易理解这些 API
与读写 API 相似的 API 还有 getBytes、getByte() 与 setBytes()、setByte() 系列,惟一的区别就是 get/set 不会改变读写指针,而 read/write 会改变读写指针,这点在解析数据的时候千万要注意
release() 与 retain()
因为 Netty 使用了堆外内存,而堆外内存是不被 jvm 直接管理的,也就是说申请到的内存没法被垃圾回收器直接回收,因此须要咱们手动回收。有点相似于c语言里面,申请到的内存必须手工释放,不然会形成内存泄漏。
Netty 的 ByteBuf 是经过引用计数的方式管理的,若是一个 ByteBuf 没有地方被引用到,须要回收底层内存。默认状况下,当建立完一个 ByteBuf,它的引用为1,而后每次调用 retain() 方法, 它的引用就加一, release() 方法原理是将引用计数减一,减完以后若是发现引用计数为0,则直接回收 ByteBuf 底层的内存。
slice()、duplicate()、copy()
这三个方法一般状况会放到一块儿比较,这三者的返回值都是一个新的 ByteBuf 对象
retainedSlice() 与 retainedDuplicate()
相信读者应该已经猜到这两个 API 的做用了,它们的做用是在截取内存片断的同时,增长内存的引用计数,分别与下面两段代码等价
// retainedSlice 等价于 slice().retain(); // retainedDuplicate() 等价于 duplicate().retain()
使用到 slice 和 duplicate 方法的时候,千万要理清内存共享,引用计数共享,读写指针不共享几个概念,下面举两个常见的易犯错的例子
Buffer buffer = xxx; doWith(buffer); // 一次释放 buffer.release(); public void doWith(Bytebuf buffer) { // ... // 没有增长引用计数 Buffer slice = buffer.slice(); foo(slice); } public void foo(ByteBuf buffer) { // read from buffer // 重复释放 buffer.release(); }
这里的 doWith 有的时候是用户自定义的方法,有的时候是 Netty 的回调方法,好比 channelRead() 等等
Buffer buffer = xxx; doWith(buffer); // 引用计数为2,调用 release 方法以后,引用计数为1,没法释放内存 buffer.release(); public void doWith(Bytebuf buffer) { // ... // 增长引用计数 Buffer slice = buffer.retainedSlice(); foo(slice); // 没有调用 release } public void foo(ByteBuf buffer) { // read from buffer }
想要避免以上两种状况发生,你们只须要记得一点,在一个函数体里面,只要增长了引用计数(包括 ByteBuf 的建立和手动调用 retain() 方法),就必须调用 release() 方法。
以上内容来源于掘金小册《Netty 入门与实战:仿写微信 IM 即时通信系统》,若想得到更多,更详细的内容,请用微信扫码订阅: