引言
API设计更建议实战过程中逐渐了解熟悉掌握,本文记录基础设计和相关API,只需要大致了解ByteBuf设计思想即可。
思维导图
基础结构
整个ByteBuf的数据结构组成如下,整个设计思想有点类似计算机如何实现从北京到上海,那就是一段足够长的铁轨,不断“拆掉”后面的铁轨放到前面的铁轨上,这样实现火车一直在铁轨上跑的错觉。
- 容量上限:maxCapacity
- 容量:capacity
- 数组:
- 废弃字节 :被丢弃的字节数据无效
- 可读字节(writerIndex -readerIndex)
- 可写字节(capacity - writerIndex)
- 读指针 readerIndex :每读取(read)一个字节,readerIndex 自增 1
- 写指针 writerIndex :每写入(write)一个字节,writeIndex 自增 1
- 剩余可用空间
结构解析
- 字节容器:分为三部分
- 废弃空间 :被丢弃的字节,数据无效
- 可读空间 :从ByteBuf读取出来的数据都属于这部分
- 可写空间 :未来所有的写入都会写入到此处
- 划分依据:两个指针加一个变量
- 读指针
- 写指针
- 总容量
- 最大容量和和容量限制
- 容量可以在写满的时候扩容
- 如果扩容到最大容量就报错
容量API
实践容量API之前,我们先构建ByteBuf。
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(9, 100); print("allocate ByteBuf(9,100) => {} \n", buffer); // allocate ByteBuf(9,100) => PooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 9/100)
capacity()
表示 ByteBuf 底层占用了多少字节的内存(包括丢弃的字节、可读字节、可写字节),不同的底层实现机制有不同的计算方式,后面我们讲 ByteBuf 的分类的时候会讲到。
从案例看出,默认创建的方式容量为初始化指定的容量。
print("allocate ByteBuf(9,100) capacity => {} \n", buffer.capacity()); // allocate ByteBuf(9,100) capacity => 9
maxCapacity()
表示 ByteBuf 底层最大能够占用多少字节的内存,当向 ByteBuf 中写数据的时候,如果发现容量不足,则进行扩容,直到扩容到 maxCapacity,超过这个数,就抛异常。
从案例可以得知,如果扩容到 100 就会报错
print("allocate ByteBuf(9,100) maxCapacity => {} \n", buffer.maxCapacity()); // allocate ByteBuf(9,100) maxCapacity => 100
readableBytes() 与 isReadable()
readableBytes() 表示 ByteBuf 当前可读的字节数,它的值等于 writerIndex-readerIndex,如果两者相等,则不可读,isReadable() 方法返回 false。
// readableBytes() 与 isReadable() print("allocate ByteBuf(9,100) isReadable => {} \n", buffer.isReadable()); print("allocate ByteBuf(9,100) readableBytes => {} \n", buffer.readableBytes()); // allocate ByteBuf(9,100) isReadable => false // allocate ByteBuf(9,100) readableBytes => 0 // write 方法改变写指针 buffer.writeBytes(new byte[]{1, 2, 3, 4}); // 改变写指针 writeBytes(new byte[]{1,2,3,4}) => PooledUnsafeDirectByteBuf(ridx: 0, widx: 4, cap: 9/100) // 写入数据之后,重新执行readableBytes() 与 isReadable() print("allocate ByteBuf(9,100) isReadable => {} \n", buffer.isReadable()); print("allocate ByteBuf(9,100) readableBytes => {} \n", buffer.readableBytes()); //allocate ByteBuf(9,100) isReadable => true //allocate ByteBuf(9,100) readableBytes => 4
writableBytes()、 isWritable() 与 maxWritableBytes()
writableBytes() 表示 ByteBuf 当前可写的字节数,它的值等于 capacity-writerIndex,如果两者相等,则表示不可写,isWritable() 返回 false。
注意这个时候,并不代表不能往 ByteBuf 中写数据了, 如果发现往 ByteBuf 中写数据写不进去的话,Netty 会自动扩容 ByteBuf,直到扩容到底层的内存大小为 maxCapacity。
maxWritableBytes() 就表示可写的最大字节数,它的值等于 maxCapacity-writerIndex。
在初始化构建过程中,由于没有读写任何数据,可以看到他们的值基本和前面计算的容量是一致的。
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(9, 100); // writableBytes()、 isWritable() 与 maxWritableBytes() print("allocate ByteBuf(9,100) writableBytes => {} \n", buffer.writableBytes()); print("allocate ByteBuf(9,100) isWritable => {} \n", buffer.isWritable()); print("allocate ByteBuf(9,100) maxWritableBytes => {} \n", buffer.maxWritableBytes()); // allocate ByteBuf(9,100) writableBytes => 9 // allocate ByteBuf(9,100) isWritable => true // allocate ByteBuf(9,100) maxWritableBytes => 100
读写指针相关的 API
实践读写指针相关的 API之前,我们先构建初始化ByteBuf。
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(9, 100); print("allocate ByteBuf(9,100) => {} \n", buffer);
readerIndex() 与 readerIndex(int)
- readerIndex():返回当前的读指针 readerIndex
- readerIndex(int):表示设置读指针
在没有写入任何数据的时候,读指针为0。
// readerIndex() 返回当前读指针 print("allocate ByteBuf(9,100) readerIndex => {} \n", buffer.readerIndex()); // allocate ByteBuf(9,100) readerIndex => 0
下面的案例说明读指针不能越过写指针的界限。
// 尝试手动重定向渎指针位置 // print("allocate ByteBuf(9,100) readerIndex(int) => {} \n", buffer.readerIndex(2)); // readerIndex: 2, writerIndex: 0 (expected: 0 <= readerIndex <= writerIndex <= capacity(9))
我们写入一些数据之后,再进行读指针重定向。
buffer.writeBytes(new byte[]{1, 2, 3, 4}); // 重定向读指针 print("allocate ByteBuf(9,100) readerIndex(int) => {} \n", buffer.readerIndex(2)); print("重定向读指针 之后 (new byte[]{1,2,3,4}) => {} \n", buffer); // allocate ByteBuf(9,100) readerIndex(int) => PooledUnsafeDirectByteBuf(ridx: 2, widx: 4, cap: 9/100) // 重定向读指针 之后 (new byte[]{1,2,3,4}) => PooledUnsafeDirectByteBuf(ridx: 2, widx: 4, cap: 9/100)
writerIndex() 与 writerIndex(int)
writeIndex()
表示返回当前的写指针 writerIndex。writeIndex(int)
表示设置写指针。
案例以初始化写入四个字节之后作为开始。
// writeIndex() 与 writeIndex(int) print("allocate ByteBuf(9,100) writerIndex => {} \n", buffer.writerIndex()); print("allocate ByteBuf(9,100) writerIndex(int) => {} \n", buffer.writerIndex(2)); // allocate ByteBuf(9,100) writerIndex => 4 // allocate ByteBuf(9,100) writerIndex(int) => PooledUnsafeDirectByteBuf(ridx: 0, widx: 2, cap: 9/100)
markReaderIndex() 与 resetReaderIndex()
区别:
markReaderIndex()
:表示把当前的读指针保存起来,resetReaderIndex()
:表示把当前的读指针恢复到之前保存的值。
下面两段代码是等价的。
// 代码片段1 int readerIndex = buffer.readerIndex(); // … 其他操作 buffer.readerIndex(readerIndex);
// 代码片段二 // (不需要自己定义变量,推荐使用) buffer.markReaderIndex(); // … 其他操作 // resetReaderIndex() 可以恢复到之前状态 // (解析自定义协议的数据包常用) buffer.resetReaderIndex();
希望大家多多使用代码片段二这种方式,不需要自己定义变量,无论 buffer 当作参数传递到哪里,调用 resetReaderIndex() 都可以恢复到之前的状态,在解析自定义协议的数据包的时候非常常见,推荐大家使用这一对 API markWriterIndex() 与 resetWriterIndex() 这一对 API 的作用与上述一对 API 类似
《跟闪电侠学Netty》阅读笔记 - 数据载体ByteBuf(二)https://developer.aliyun.com/article/1395306