Netty的零拷贝区别于操作系统的零拷贝,而更偏向于一种数据处理过程中的优化操作
Netty的Zero-copy体现在如下几个方面:
- Netty提供了CompositeByteBuf类,它可以将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了各个ByteBuf之间的拷贝。
- 通过wrap操作,我们可以将byte[]数组、ByteBuf、 ByteBuffer 等包装成一个 Netty ByteBuf对象,进而避免了拷贝操作。
- ByteBuf支持slice 操作,因此可以将ByteBuf分解为多个共享同一个存储区域的ByteBuf,避免了内存的拷贝。
- 通过FileRegion包装的FileChannel.tranferTo实现文件传输,可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。
现在我们来用wrap举个例子简单分析下:
byte[] b1 = new byte[]{1, 2};
// 直接使用Unpooled.buffer()
ByteBuf buf = Unpooled.buffer(b1.length);
buf.writeBytes(b1);
b1[0] = 0; // 改变数组,观察buf对象是否受其影响
System.out.println("===1 : b1 = " + Arrays.toString(b1));
System.out.println("===1 : buf1 = " + buf.readByte());
System.out.println("===1 : buf2 = " + buf.readByte());
// 使用wrap操作
ByteBuf buf1 = Unpooled.wrappedBuffer(b1);
b1[0] = 3; // 改变数组,观察buf对象是否受其影响
System.out.println("===2 : b1 = " + Arrays.toString(b1));
System.out.println("===2 : buf1 = " + buf1.readByte());
System.out.println("===2 : buf2 = " + buf1.readByte());
// 打印结果
===1 : b1 = [0, 2]
===1 : buf1 = 1
===1 : buf2 = 2
===2 : b1 = [3, 2]
===2 : buf1 = 3
===2 : buf2 = 2
对比结果很明显,wrap操作时,当数组b1发生变化,buf1对象也会相应发生改变,而buffer()则不受影响。
我们来简单看下二者具体操作:
public static ByteBuf buffer(int initialCapacity) {
return ALLOC.heapBuffer(initialCapacity);
}
buffer()会在堆内存中新开辟一块长度为 initialCapacity 的内存空间,用来存储b1,这样当我们对数组b1进行改变操作时,显然buf对象不会受其影响,接下来我们对比下wrap操作:
public static ByteBuf wrappedBuffer(byte[] array) {
if (array.length == 0) {
return EMPTY_BUFFER;
}
return new UnpooledHeapByteBuf(ALLOC, array, array.length);
}
protected UnpooledHeapByteBuf(ByteBufAllocator alloc, byte[] initialArray, int maxCapacity) {
super(maxCapacity);
checkNotNull(alloc, "alloc");
checkNotNull(initialArray, "initialArray");
if (initialArray.length > maxCapacity) {
throw new IllegalArgumentException(String.format(
"initialCapacity(%d) > maxCapacity(%d)", initialArray.length, maxCapacity));
}
this.alloc = alloc;
setArray(initialArray);
setIndex(0, initialArray.length);
}
由此可见,wrap会将数组b1直接存放到 UnpooledHeapByteBuf 对象中,从而避免了直接进行拷贝操作。