ByteBuf的可读字节数存储了实际数据。新分配的、包装的或者负责的缓冲区的默认的readerIndex值为0.任何名称以read或者skip开头的操作都将会检索或跳过位于当前readIndex的数据,并且将它增加已读字节数。
如果被调用的方法需要一个ByteBuf
参数作为写入的目标,并且没有指定目标索引参数,那么该目标缓冲区的writerIndex
也将被增加,例如:
readBytes(ByteBuf dest);
如果尝试在缓冲区的可读字节数已经耗尽时从中读取数据,那么将会引发一个IndexOutOf-BoundsException
。
下面的示例代码展示了如何读取所有可以读的字节。
/**
* 测试读取所有的可读字节
*/
@Test
public void readAbleByte() {
ByteBuf byteBuf = Unpooled.buffer(16);
byteBuf.writeBytes("abcdefg".getBytes());
while (byteBuf.isReadable()) {
System.out.print( (char)byteBuf.readByte());
}
}
可写字节分段是指一个拥有未定义内容的、写入就绪的内存区域。新分配的缓冲区的writerIndex
的默认值为0。任何名称以write
开头的操作都将从当前的writerIndex
处开始写数据,并将它增加已经写入的字节数。如果写操作的目标也是ByteBuf
,并且没有指定源索引的值,则源缓冲区的readerIndex
也同样会被增加相同的大小。这个调用如下所示:
writeBytes(ByteBuf dest);
如果尝试往目标写入超过目标容量的数据,将会引发一个IndexOutOfBoundException
[5]。
以下示例是一个写入字节的示例:
/**
* 测试写入所有的数据
*/
@Test
public void writeByte(){
ByteBuf buf = Unpooled.buffer(16);
Random random = new Random();
while(buf.writableBytes() >= 4){
buf.writeInt(random.nextInt());
}
}
JDK的inputStream定义了mark(int readlimit)和reset()方法,这些方法分别被用来将流中的当前位置标记为指定的值,以及将该流重置到该位置。
同样,可以通过调用markReadIndex()、markWriterIndex()、restWriterIndex()和resetReaderIndex()来标记和重置ByteBuf的ReaderIndex及WriterIndex。
可以通过调用readerIndex(int)、writerIndex(int)来将索引移动到指定位置。
还可以通过调用clear()方法来将readerIndex和writerIndex的值设置为0。注意,这并不会清除内存中的内容。
调用clear()方法比discardBytes()轻量得多,因为它将只是重置索引而不会复制任何得内存。
/**
* 测试ByteBuf得clear方法
*/
@Test
public void testClear(){
ByteBuf byteBuf = Unpooled.buffer(16);
byteBuf.writeBytes("abcdefg".getBytes());
//从符合缓冲区中读取数据
byte[] bytes = new byte[byteBuf.readableBytes()-1];
byteBuf.readBytes(bytes);
System.out.println(new String(bytes));
System.out.println("调用clear方法前,readerIndex:" + byteBuf.readerIndex() + ",writerIndex:" + byteBuf.writerIndex() );
byteBuf.clear();
System.out.println("调用clear方法后,readerIndex:" + byteBuf.readerIndex() + ",writerIndex:" + byteBuf.writerIndex() );
}
运行上面得示例,输出如下:
调用clear方法前,readerIndex:6,writerIndex:7
调用clear方法后,readerIndex:0,writerIndex:0
在ByteBuf
中有多种可以用来确定指定值的索引的方法。最简单的是使用indexOf()
方法。较复杂的查找可以通过那些需要一个ByteBufProcessor
[6]作为参数的方法达成。这个接口只定义了一个方法:
boolean process(byte value)
它将检查输入值是否是正在查找的值。
ByteBufProcessor
针对一些常见的值定义了许多便利的方法。假设你的应用程序需要和所谓的包含有以 NULL 结尾的内容的 Flash 套接字[7]集成。调用
forEachByte(ByteBufProcessor.FIND_NUL)
将简单高效地消费该 Flash 数据,因为在处理期间只会执行较少的边界检查。
下面的示例展示了一个查找回车符(\r
)的例子。
/**
* 测试查找操作
*/
@Test
public void testProcess(){
ByteBuf byteBuf = Unpooled.buffer(16);
byteBuf.writeBytes("ab\rcdefg\r\r".getBytes());
int index = byteBuf.forEachByte(ByteProcessor.FIND_CR);
System.out.println(index);
}
派生缓冲区为ByteBuf
提供了以专门的方式来呈现其内容的视图。这类视图是通过以下方法被创建的:
duplicate();
slice();
slice(int, int);
Unpooled.unmodifiableBuffer(…);
order(ByteOrder);
readSlice(int)。
每个这些方法都将返回一个新的ByteBuf
实例,它具有自己的读索引、写索引和标记索引。其内部存储和 JDK 的ByteBuffer
一样也是共享的。这使得派生缓冲区的创建成本是很低廉的,但是这也意味着,如果你修改了它的内容,也同时修改了其对应的源实例,所以要小心。
ByteBuf复制
如果需要一个现有缓冲区的真实副本,请使用
copy()
或者copy(int, int)
方法。不同于派生缓冲区,由这个调用所返回的ByteBuf
拥有独立的数据副本。
/**
* 测试数据的分片
*/
@Test
public void testSlice(){
Charset utf8 = Charset.forName("UTF-8");
ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); // 创建一个用于保存给定字符串的字节的 ByteBuf
ByteBuf sliced = buf.slice(0, 15); // 创建该 ByteBuf 从索引0 开始到索引15结束的一个新切片
System.out.println(sliced.toString(utf8)); // 将打印“Netty in Action”
buf.setByte(0, (byte)'J'); // 更新索引0 处的字节
assert buf.getByte(0) == sliced.getByte(0);// 将会成功,因为数据是共享的,对其中一个所做的更改对另外一个也是可见的
}
下表是一些有用的操作
名 称 | 描 述 |
---|---|
isReadable() |
如果至少有一个字节可供读取,则返回true |
isWritable() |
如果至少有一个字节可被写入,则返回true |
readableBytes() |
返回可被读取的字节数 |
writableBytes() |
返回可被写入的字节数 |
capacity() |
返回ByteBuf 可容纳的字节数。在此之后,它会尝试再次扩展直 到达到maxCapacity() |
maxCapacity() |
返回ByteBuf 可以容纳的最大字节数 |
hasArray() |
如果ByteBuf 由一个字节数组支撑,则返回true |
array() |
如果 ByteBuf 由一个字节数组支撑则返回该数组;否则,它将抛出一个UnsupportedOperationException 异常 |
我们经常发现,除了实际的数据负载之外,还需要存储各种属性。
ByteBufHolder
的操作
名 称 | 描 述 |
---|---|
content() |
返回由这个ByteBufHolder 所持有的ByteBuf |
copy() |
返回这个ByteBufHolder 的一个深拷贝,包括一个其所包含的ByteBuf 的非共享副本 |
duplicate() |
返回这个ByteBufHolder 的一个浅拷贝,包括一个其所包含的ByteBuf 的共享副本 |
如果想要实现一个将其有效负载存储在ByteBuf
中的消息对象,那么ByteBufHolder
将是个不错的选择。
在这一节中,我们将描述管理ByteBuf
实例的不同方式。
1、按需分配:ByteBufAllocator
为了降低分配和释放内存的开销,Netty 通过interface ByteBufAllocator
实现了(ByteBuf
的)池化,它可以用来分配我们所描述过的任意类型的ByteBuf
实例。
通常我们可以通过Channel(每个都可以有一个同步的ByteBufAllocator实例)或者绑定到ChannelHandler的ChannelHandlerContext获取一个到ByteBufAllocator的引用:
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
Channel channel = ctx.channel();
ByteBufAllocator alloc = ctx.alloc();
ByteBufAllocator alloc1 = channel.alloc();
ctx.flush();
}
netty提供了两种ByteBufAllocator的实现:PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片。
虽然 Netty 默认使用了PooledByteBufAllocator
,但这可以很容易地通过Channel-Config
API 或者在引导你的应用程序时指定一个不同的分配器来更改。
@Test
public void testUnpooledByteBufAllocator(){
UnpooledByteBufAllocator allocator = new UnpooledByteBufAllocator(true);
ByteBuf buffer = allocator.buffer();
buffer.writeBytes("hello netty".getBytes());
byte[] bytes = new byte[buffer.readableBytes()];
buffer.readBytes(bytes);
System.out.println(new String(bytes));
}
可能某些情况下,你未能获取一个到ByteBufAllocator
的引用。对于这种情况,Netty 提供了一个简单的称为Unpooled
的工具类,它提供了静态的辅助方法来创建未池化的ByteBuf
实例
名 称 | 描 述 |
---|---|
buffer() buffer(int initialCapacity) buffer(int initialCapacity, int maxCapacity) |
返回一个未池化的基于堆内存存储的ByteBuf |
directBuffer() directBuffer(int initialCapacity) directBuffer(int initialCapacity, int maxCapacity) |
返回一个未池化的基于直接内存存储的ByteBuf |
wrappedBuffer() |
返回一个包装了给定数据的ByteBuf |
copiedBuffer() |
返回一个复制了给定数据的ByteBuf |
Unpooled
类还使得ByteBuf
同样可用于那些并不需要 Netty 的其他组件的非网络项目,使得其能得益于高性能的可扩展的缓冲区 API。