Netty提供的ByteBuf与JDK的ByteBuffer相比,前者具有卓越的功能性和灵活性。
ByteBuf提供读访问索引(readerIndex)和写访问索引(writerIndex)来控制字节数组。ByteBuf API具有以下优点:
ByteBuf维护两个不同的索引: 读索引(readerIndex)和写索引(writerIndex)。如下图:
ByteBuf本质是: 一个由不同的索引分别控制读访问和写访问的字节数组。
请记住这句话。ByteBuf共有三种模式: 堆缓冲区模式(Heap Buffer)、直接缓冲区模式(Direct Buffer)和复合缓冲区模式(Composite Buffer)
堆缓冲区模式又称为:支撑数组(backing array)。将数据存放在JVM的堆空间,通过将数据存储在数组中实现
能在没有使用池化的情况下提供快速的分配和释放。
/** * 代码清单 5-1 堆缓冲区 */ public static void heapBuffer() { // 创建Java堆缓冲区 ByteBuf heapBuf = Unpooled.buffer(); //检查ByteBuf是否有一个支撑数组 //当 hasArray()方法返回 false 时,尝试访问支撑数组将触发一个 Unsupported OperationException。这个模式类似于 JDK 的 ByteBuffer 的用法。 if (heapBuf.hasArray()) { byte[] array = heapBuf.array(); //计算第一个字节的偏移量 int offset = heapBuf.arrayOffset() + heapBuf.readerIndex(); int length = heapBuf.readableBytes(); //使用数组、偏移量和长度作为参数调用你的方法 handleArray(array, offset, length); } }
Direct Buffer属于堆外分配的直接内存,不会占用堆的容量。适用于套接字传输过程,避免了数据从内部缓冲区拷贝到直接缓冲区的过程,性能较好
public static void directBuffer() { ByteBuf directBuf = Unpooled.directBuffer(); if (!directBuf.hasArray()) { int length = directBuf.readableBytes(); byte[] array = new byte[length]; directBuf.getBytes(directBuf.readerIndex(), array); handleArray(array, 0, length); } }
Composite Buffer是Netty特有的缓冲区。本质上类似于提供一个或多个ByteBuf的组合视图,可以根据需要添加和删除不同类型的ByteBuf。
警告 CompositeByteBuf 中的 ByteBuf 实例可能同时包含直接内存分配和非直接内存分配。
如果其中只有一个实例,那么对 CompositeByteBuf 上的 hasArray()方法的调用将返回该组
件上的 hasArray()方法的值;否则它将返回 false。
下面代码使用 JDK 的 ByteBuffer 来实现这一需求。
创建了一个包含两个 ByteBuffer 的数组用来保存这些消息组件,
同时创建了第三个 ByteBuffer 用来保存所有这些数据的副本。
// Use an array to hold the message parts ByteBuffer[] message = new ByteBuffer[] { header, body }; // Create a new ByteBuffer and use copy to merge the header and body ByteBuffer message2 = ByteBuffer.allocate(header.remaining() + body.remaining()); message2.put(header); message2.put(body); message2.flip();
分配和复制操作,以及伴随着对数组管理的需要,使得这个版本的实现效率低下而且笨拙。
下面展示了一个使用了 CompositeByteBuf 的版本。
public static void byteBufComposite() { // 复合缓冲区,只是提供一个视图 CompositeByteBuf messageBuf = Unpooled.compositeBuffer(); ByteBuf headerBuf = Unpooled.buffer(); // can be backing or direct ByteBuf bodyBuf = Unpooled.directBuffer(); // can be backing or direct messageBuf.addComponents(headerBuf, bodyBuf); messageBuf.removeComponent(0); // remove the header for (ByteBuf buf : messageBuf) { System.out.println(buf.toString()); } }
CompositeByteBuf 可能不支持访问其支撑数组,因此访问 CompositeByteBuf 中的数据类似于(访问)直接缓冲区的模式,如下
CompositeByteBuf compBuf = Unpooled.compositeBuffer(); int length = compBuf.readableBytes(); byte[] array = new byte[length]; compBuf.getBytes(compBuf.readerIndex(), array); handleArray(array, 0, array.length);
Netty使用了CompositeByteBuf来优化套接字的I/O操作,尽可能地消除了
由JDK的缓冲区实现所导致的性能以及内存使用率的惩罚
ByteBuf的索引与普通的Java字节数组一样。第一个字节的索引是0,最后一个字节索引总是capacity()-1。请记住下列两条,非常有用:
public static void byteBufRelativeAccess() { ByteBuf buffer = Unpooled.buffer(); //get reference form somewhere for (int i = 0; i < buffer.capacity(); i++) { byte b = buffer.getByte(i);// 不改变readerIndex值 System.out.println((char) b); } }
Netty的ByteBuf同时具有读索引和写索引,但JDK的ByteBuffer只有一个索引,所以JDK需要调用flip()方法在读模式和写模式之间切换。
可丢弃字节区域是指:[0,readerIndex)之间的区域。可调用discardReadBytes()方法丢弃已经读过的字节。
可读字节区域是指:[readerIndex, writerIndex)之间的区域。任何名称以read和skip开头的操作方法,都会改变readerIndex索引。
可写字节区域是指:[writerIndex, capacity)之间的区域。任何名称以write开头的操作方法都将改变writerIndex的值。
查找ByteBuf指定的值。类似于,String.indexOf("str")操作
public static void byteProcessor() { ByteBuf buffer = Unpooled.buffer(); //get reference form somewhere // 使用indexOf()方法来查找 buffer.indexOf(buffer.readerIndex(), buffer.writerIndex(), (byte)8); // 使用ByteProcessor查找给定的值 int index = buffer.forEachByte(ByteProcessor.FIND_CR); }
派生缓冲区为ByteBuf提供了一个访问的视图。视图仅仅提供一种访问操作,不做任何拷贝操作。下列方法,都会呈现给使用者一个视图,以供访问:
public static void byteBufSlice() { Charset utf8 = Charset.forName("UTF-8"); ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); ByteBuf sliced = buf.slice(0, 15); System.out.println(sliced.toString(utf8)); buf.setByte(0, (byte)'J'); assert buf.getByte(0) == sliced.getByte(0); // return true } public static void byteBufCopy() { Charset utf8 = Charset.forName("UTF-8"); ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); ByteBuf copy = buf.copy(0, 15); System.out.println(copy.toString(utf8)); buf.setByte(0, (byte)'J'); assert buf.getByte(0) != copy.getByte(0); // return true }
如上文所提到的,有两种类别的读/写操作:
下面的两个方法操作字面意思较难理解,给出解释:
ByteBufHolder为Netty的高级特性提供了支持,如缓冲区池化,可以从池中借用ByteBuf,并且在需要时自动释放。
public class CustomByteBufHolder extends DefaultByteBufHolder{ private String protocolName; public CustomByteBufHolder(String protocolName, ByteBuf data) { super(data); this.protocolName = protocolName; } @Override public CustomByteBufHolder replace(ByteBuf data) { return new CustomByteBufHolder(protocolName, data); } @Override public CustomByteBufHolder retain() { super.retain(); return this; } @Override public CustomByteBufHolder touch() { super.touch(); return this; } @Override public CustomByteBufHolder touch(Object hint) { super.touch(hint); return this; } ... }
创建和管理ByteBuf实例的多种方式:按需分配(ByteBufAllocator)、Unpooled缓冲区和ByteBufUtil类
Netty通过接口ByteBufAllocator实现了(ByteBuf的)池化。Netty提供池化和非池化的ButeBufAllocator:
Unpooled提供静态的辅助方法来创建未池化的ByteBuf。
public void createByteBuf(ChannelHandlerContext ctx) { // 1. 通过Channel创建ByteBuf ByteBuf buf1 = ctx.channel().alloc().buffer(); // 2. 通过ByteBufAllocator.DEFAULT创建 ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer(); // 3. 通过Unpooled创建 ByteBuf buf3 = Unpooled.buffer(); }
ByteBufUtil类提供了用于操作ByteBuf的静态的辅助方法: hexdump()和equals
Netty4.0版本中为ButeBuf和ButeBufHolder引入了引用计数技术。请区别引用计数和可达性分析算法(jvm垃圾回收)
public static void releaseReferenceCountedObject(){ ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); // 引用计数加1 buffer.retain(); // 输出引用计数 buffer.refCnt(); // 引用计数减1 buffer.release(); }