Netty 的数据处理API 通过两个组件暴露——abstract class ByteBuf和interfaceByteBufHolder。
ByteBuf 维护了两个不同的索引:一个用于读取,一个用于写入。当你从ByteBuf 读取时,它的readerIndex 将会被递增已经被读取的字节数。同样地,当你写入ByteBuf 时,它的writerIndex 也会被递增。图5-1展示了一个空ByteBuf 的布局结构和状态。
名称以read 或者write 开头的ByteBuf 方法,将会推进其对应的索引,而名称以set 或者get 开头的操作则不会。后面的这些方法将在作为一个参数传入的一个相对索引上执行操作。
在使用Netty 时,你将遇到几种常见的围绕ByteBuf 而构建的使用模式。
最常用的ByteBuf 模式是将数据存储在JVM 的堆空间中。这种模式被称为支撑数组(backingarray),它能在没有使用池化的情况下提供快速的分配和释放。
直接缓冲区是另外一种ByteBuf 模式。我们期望用于对象创建的内存分配永远都来自于堆中,但这并不是必须的——NIO 在JDK 1.4 中引入的ByteBuffer 类允许JVM 实现通过本地调用来分配内存。这主要是为了避免在每次调用本地I/O 操作之前(或者之后)将缓冲区的内容复制到一个中间缓冲区(或者从中间缓冲区把内容复制到缓冲区)。
直接缓冲区的主要缺点是,相对于基于堆的缓冲区,它们的分配和释放都较为昂贵。如果你正在处理遗留代码,你也可能会遇到另外一个缺点:因为数据不是在堆上,所以你不得不进行一次复制。
第三种也是最后一种模式使用的是复合缓冲区,它为多个ByteBuf 提供一个聚合视图。在这里你可以根据需要添加或者删除ByteBuf 实例,这是一个JDK 的ByteBuffer 实现完全缺失的特性。
Netty 通过一个ByteBuf 子类——CompositeByteBuf——实现了这个模式,它提供了一个将多个缓冲区表示为单个合并缓冲区的虚拟表示。
注意:CompositeByteBuf 中的ByteBuf 实例可能同时包含直接内存分配和非直接内存分配。
,那么对CompositeByteBuf 上的hasArray()方法的调用将返回该组件上的hasArray()方法的值;否则它将返回false。
使用CompositeByteBuf 的复合缓冲区模式:
访问CompositeByteBuf 中的数据
如同在普通的Java 字节数组中一样,ByteBuf 的索引是从零开始的:第一个字节的索引是0,最后一个字节的索引总是capacity() - 1。
public static void main(String[] args) {
ByteBufAllocator byteBufAllocator = new UnpooledByteBufAllocator(true);
ByteBuf buffer = byteBufAllocator.buffer(3);
buffer.setByte(0,0);
buffer.setByte(1,1);
buffer.setByte(2,2);
randomGetIndex(buffer);
}
/**
* 随机访问索引
* @param byteBuf
*/
public static void randomGetIndex(ByteBuf byteBuf) {
for (int i=0; i< byteBuf.capacity(); i++) {
byte aByte = byteBuf.getByte(i);
System.out.println(aByte);
}
}
ByteBuf被它的两个索引划分为了三个区域,如图所示:
在上图中标记为可丢弃字节的分段包含了已经被读过的字节。通过调用discardRead-Bytes()方法,可以丢弃它们并回收空间。这个分段的初始大小为0,存储在readerIndex 中,会随着read 操作的执行而增加(get*操作不会移动readerIndex)。
ByteBuf的可读字节分段存储了实际数据。新分配的、包装的或者复制的缓冲区的默认的readerIndex的值为0,任何名称以read 或者skip 开头的操作都将检索或者跳过位于当前readerIndex 的数据,并且将它增加已读字节数。
/**
* 可写字节
* @param byteBuf
* @return
*/
public static ByteBuf writeByte(ByteBuf byteBuf) {
Random random = new Random();
while (byteBuf.writableBytes() >= 4) {
int nextInt = random.nextInt();
System.out.println("write======"+nextInt);
byteBuf.writeInt(nextInt);
}
System.out.println(JSONObject.toJSONString(byteBuf));
return byteBuf;
}
同样,可以通过调用markReaderIndex()、markWriterIndex()、resetWriterIndex()和resetReaderIndex()来标记和重置ByteBuf 的readerIndex 和writerIndex。这些和InputStream 上的调用类似,只是没有readlimit 参数来指定标记什么时候失效。
也可以通过调用readerIndex(int)或者writerIndex(int)来将索引移动到指定位置。试图将任何一个索引设置到一个无效的位置都将导致一个IndexOutOfBoundsException。可以通过调用clear()方法来将readerIndex 和writerIndex 都设置为0。
派生缓冲区为ByteBuf 提供了以专门的方式来呈现其内容的视图。这类视图是通过以下方法被创建的:
duplicate();
slice();
slice(int, int);
Unpooled.unmodifiableBuffer(…);
order(ByteOrder);
readSlice(int)。
每个这些方法都将返回一个新的ByteBuf 实例,它具有自己的读索引、写索引和标记索引。其内部存储和JDK 的ByteBuffer 一样也是共享的。这使得派生缓冲区的创建成本是很低廉的,但是这也意味着,如果你修改了它的内容,也同时修改了其对应的源实例,所以要小心。
对于Netty有两种类型的读写操作:
get()和set()操作,从给定的索引开始,并且保持索引不变;
read()和write()操作,从给定的索引开始,并且会根据已经访问过的字节数对索引进行调整。
名称 | 描述 |
---|---|
getBoolean(int) | 返回给定索引处的Boolean 值 |
getByte(int) | 返回给定索引处的字节 |
getUnsignedByte(int) | 将给定索引处的无符号字节值作为short 返回 |
getMedium(int) | 返回给定索引处的24 位的中等int 值 |
getUnsignedMedium(int) | 返回给定索引处的无符号的24 位的中等int 值 |
getInt(int) | 返回给定索引处的int 值 |
getUnsignedInt(int) | 将给定索引处的无符号int 值作为long 返回 |
getLong(int) | 返回给定索引处的long 值 |
getShort(int) | 返回给定索引处的short 值 |
getUnsignedShort(int) | 将给定索引处的无符号short 值作为int 返回 |
getBytes(int, …) | 将该缓冲区中从给定索引开始的数据传送到指定的目的地 |
名称 | 描述 |
---|---|
setBoolean(int, boolean) | 设定给定索引处的Boolean 值 |
setByte(int index, int value) | 设定给定索引处的字节值 |
setMedium(int index, int value) | 设定给定索引处的24 位的中等int 值 |
setInt(int index, int value) | 设定给定索引处的int 值 |
setLong(int index, long value) | 设定给定索引处的long 值 |
setShort(int index, int value) | 设定给定索引处的short 值 |
名称 | 描述 |
---|---|
readBoolean() | 返回当前readerIndex 处的Boolean,并将readerIndex 增加1 |
readByte() | 返回当前readerIndex 处的字节,并将readerIndex 增加1 |
readUnsignedByte() | 将当前readerIndex 处的无符号字节值作为short 返回,并将readerIndex 增加1 |
readMedium() | 返回当前readerIndex 处的24 位的中等int 值,并将readerIndex增加3 |
readUnsignedMedium() | 返回当前readerIndex 处的24 位的无符号的中等int 值,并将readerIndex 增加3 |
readInt() | 返回当前readerIndex 的int 值,并将readerIndex 增加4 |
readUnsignedInt() | 将当前readerIndex 处的无符号的int 值作为long 值返回,并将readerIndex 增加4 |
readLong() | 返回当前readerIndex 处的long 值,并将readerIndex 增加8 |
readShort() | 返回当前readerIndex 处的short 值,并将readerIndex 增加2 |
readUnsignedShort() | 将当前readerIndex 处的无符号short 值作为int 值返回,并将readerIndex 增加2 |
readBytes(ByteBuf byte[] destination,int dstIndex [,intlength]) | 将当前ByteBuf 中从当前readerIndex 处开始的(如果设置了,length 长度的字节)数据传送到一个目标ByteBuf 或者byte[],从目标的dstIndex 开始的位置。本地的readerIndex 将被增加已经传输的字节数 |
名称 | 操作 |
---|---|
writeBoolean(boolean) | 在当前writerIndex 处写入一个Boolean,并将writerIndex 增加1 |
writeByte(int) | 在当前writerIndex 处写入一个字节值,并将writerIndex 增加1 |
writeMedium(int) | 在当前writerIndex 处写入一个中等的int 值,并将writerIndex增加3 |
writeInt(int) | 在当前writerIndex 处写入一个int 值,并将writerIndex 增加4 |
writeLong(long) | 在当前writerIndex 处写入一个long 值,并将writerIndex 增加8 |
writeShort(int) | 在当前writerIndex 处写入一个short 值,并将writerIndex 增加2 |
writeBytes(sourceByteBuf byte[][,int srcIndex,int length]) | 从当前writerIndex 开始,传输来自于指定源(ByteBuf 或者byte[])的数据。如果提供了srcIndex 和length,则从srcIndex 开始读取,并且处理长度为length 的字节。当前writerIndex 将会被增加所写入的字节数 |
名称 | 操作 |
---|---|
isReadable() | 如果至少有一个字节可供读取,则返回true |
isWritable() | 如果至少有一个字节可被写入,则返回true |
readableBytes() | 返回可被读取的字节数 |
writableBytes() | 返回可被写入的字节数 |
capacity() | 返回ByteBuf 可容纳的字节数。在此之后,它会尝试再次扩展直到达到maxCapacity() |
maxCapacity() | 返回ByteBuf 可以容纳的最大字节数 |
hasArray() | 如果ByteBuf 由一个字节数组支撑,则返回true |
array() | 如果 ByteBuf 由一个字节数组支撑则返回该数组;否则,它将抛出一个UnsupportedOperationException 异常 |
我们经常发现,除了实际的数据负载之外,我们还需要存储各种属性值。HTTP 响应便是一个很好的例子,除了表示为字节的内容,还包括状态码、cookie 等。
为了处理这种常见的用例,Netty 提供了ByteBufHolder。ByteBufHolder 也为Netty 的高级特性提供了支持,如缓冲区池化,其中可以从池中借用ByteBuf,并且在需要时自动释放。
为了降低分配和释放内存的开销,Netty 通过interface ByteBufAllocator 实现了(ByteBuf 的)池化,它可以用来分配我们所描述过的任意类型的ByteBuf 实例。使用池化是特定于应用程序的决定,其并不会以任何方式改变ByteBuf API(的语义)。
名称 | 描述 |
---|---|
buffer()buffer(int initialCapacity);buffer(int initialCapacity, int maxCapacity); | 返回一个基于堆或者直接内存存储的ByteBuf |
heapBuffer()heapBuffer(int initialCapacity)heapBuffer(int initialCapacity, int maxCapacity) | 返回一个基于堆内存存储的 |
ByteBufdirectBuffer()directBuffer(int initialCapacity)directBuffer(int initialCapacity, int maxCapacity) | 返回一个基于直接内存存储的ByteBuf |
compositeBuffer()compositeBuffer(int maxNumComponents)compositeDirectBuffer()compositeDirectBuffer(int maxNumComponents);compositeHeapBuffer()compositeHeapBuffer(int maxNumComponents); | 返回一个可以通过添加最大到指定数目的基于堆的或者直接内存存储的缓冲区来扩展的CompositeByteBuf |
ioBuffer()① | 返回一个用于套接字的I/O 操作的ByteBuf |
可以通过Channel(每个都可以有一个不同的ByteBufAllocator 实例)或者绑定ChannelHandler 的ChannelHandlerContext 获取一个到ByteBufAllocator 的引用。
Netty提供了两种ByteBufAllocator的实现:PooledByteBufAllocator和Unpooled-ByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片。此实现使用了一种称为jemalloc②的已被大量现代操作系统所采用的高效方法来分配内存。后者的实现不池化ByteBuf实例,并且在每次它被调用时都会返回一个新的实例。虽然Netty默认①5.5.2 Unpooled 缓冲区使用了PooledByteBufAllocator,但这可以很容易地通过Channel-Config API或者在引导你的应用程序时指定一个不同的分配器来更改
可能某些情况下,你未能获取一个到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 |
ByteBufUtil 提供了用于操作ByteBuf 的静态的辅助方法。因为这个API 是通用的,并且和池化无关,所以这些方法已然在分配类的外部实现。