netty(十三)初识Netty - ByteBuf 拷贝 及
Netty基于不同的使用场景,提供了几个ByteBuf当中零拷贝的方法。这些方法和我们在NIO当中谈到的不同,在NIO当中的零拷贝最终是为了减少用户态和内核态之间的数据拷贝。
本章节我们针对Netty提供的几个方法进行简单学习和使用。
一、零拷贝
先声明一点,如果要使用以下方法的话,可能要配合前面文章介绍的retain()方法去增加引用计数,否则原ByteBuf被release()后,则会导致我们拷贝出来的buf使用失败,导致异常。
1.1 slice
“零拷贝”的体现之一,对原始 ByteBuf 进行切片,切分成多个 ByteBuf,切片后的 ByteBuf 并没有发生内存复制,只是多了引用到分片后的Bytebuf,然而还是使用的原始 ByteBuf 的内存,切片后的 ByteBuf 维护独立的 read,write 指针,修改子分片,会修改原ByteBuf。
示例代码:
public static void main(String[] args) {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{1,2,3,4,5,6,7,8,9,0});
printBuf(byteBuf);
//分片1
ByteBuf slice1 = byteBuf.slice(0, 5);
printBuf(slice1);
//分片2
ByteBuf slice2 = byteBuf.slice(5, 5);
printBuf(slice2);
//将最后一位0修改成10
slice2.setByte(4,10);
printBuf(slice2);
//打印修改后的byteBuf
printBuf(byteBuf);
}
static void printBuf(ByteBuf byteBuf){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i< byteBuf.writerIndex();i++) {
stringBuilder.append(byteBuf.getByte(i));
}
System.out.println(stringBuilder);
}
结果:
1234567890
12345
67890
678910
12345678910
注意:slice后的分片,不能再次写入新的数据,这回影响原ByteBuf。
1.2、duplicate
“零拷贝”的体现之一,拷贝了原始 ByteBuf 所有内容,长度仍然以byteBuf为准,不能写入新数据,也是与原始 ByteBuf 使用同一块底层内存,只是读写指针是独立的。
使用示例:
public class DuplicateTest {
public static void main(String[] args) {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{1,2,3,4,5,6,7,8,9,0});
//拷贝一块buf
ByteBuf duplicate = byteBuf.duplicate();
printBuf(duplicate);
//将最后一位0修改成10,看一下byteBuf
duplicate.setByte(9,10);
printBuf(byteBuf);
// 写入新数据11,看byteBuf
duplicate.writeByte(11);
printBuf(byteBuf);
}
static void printBuf(ByteBuf byteBuf){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i< byteBuf.writerIndex();i++) {
stringBuilder.append(byteBuf.getByte(i));
}
System.out.println(stringBuilder);
}
}
1.3 CompositeByteBuf
“零拷贝”的体现之一,可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免拷贝。
public class CompositeByteBufTest {
public static void main(String[] args) {
ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.buffer(5);
byteBuf1.writeBytes(new byte[]{1, 2, 3, 4, 5});
ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.buffer(5);
byteBuf2.writeBytes(new byte[]{6, 7, 8, 9, 0});
CompositeByteBuf compositeByteBuf = ByteBufAllocator.DEFAULT.compositeBuffer();
// 组合两个byteBuf,主要要使用带有increaseWriteIndex的,否则会失败。
compositeByteBuf.addComponents(true,byteBuf1, byteBuf2);
printBuf(compositeByteBuf);
}
static void printBuf(ByteBuf byteBuf){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i< byteBuf.writerIndex();i++) {
stringBuilder.append(byteBuf.getByte(i));
}
System.out.println(stringBuilder);
}
}
1.4 Unpooled
Unpooled 是一个工具类,提供了非池化的 ByteBuf 创建、组合、复制等操作。
这里仅介绍其跟“零拷贝”相关的 wrappedBuffer 方法
使用示例:
public static void main(String[] args) {
ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.buffer(5);
byteBuf1.writeBytes(new byte[]{1, 2, 3, 4, 5});
ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.buffer(5);
byteBuf2.writeBytes(new byte[]{6, 7, 8, 9, 0});
// CompositeByteBuf compositeByteBuf = ByteBufAllocator.DEFAULT.compositeBuffer();
// // 组合两个byteBuf,主要要使用带有increaseWriteIndex的,否则会失败。
// compositeByteBuf.addComponents(true,byteBuf1, byteBuf2);
// 组合两个byteBuf,底层使用CompositeByteBuf。
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(byteBuf1, byteBuf2);
printBuf(wrappedBuffer);
}
static void printBuf(ByteBuf byteBuf){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i< byteBuf.writerIndex();i++) {
stringBuilder.append(byteBuf.getByte(i));
}
System.out.println(stringBuilder);
}
二、深度拷贝
ByteBuf提供了copy方法,这一类方法是真正的拷贝原ByteBuf到新的内存,返回一个新的ByteBuf,与原ByteBuf没有关系。
提供两个拷贝,一个是全量;一个指定位置和长度。
public abstract ByteBuf copy();
public abstract ByteBuf copy(int index, int length);
使用示例:
public class CopyTest {
public static void main(String[] args) {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
byteBuf.writeBytes(new byte[]{1,2,3,4,5,6,7,8,9,0});
ByteBuf copy1 = byteBuf.copy();
printBuf(copy1);
ByteBuf copy2 = byteBuf.copy(5, 5);
printBuf(copy2);
}
static void printBuf(ByteBuf byteBuf){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i< byteBuf.writerIndex();i++) {
stringBuilder.append(byteBuf.getByte(i));
}
System.out.println(stringBuilder);
}
}
关于Bytebuf的入门就介绍这么多了,后面会深入去探讨更细节的内容。
Bytebuf简单总结:
- 池化 - 可以重用池中 ByteBuf 实例,更节约内存,减少内存溢出的可能
- 读写指针分离,不需要像 ByteBuffer 一样切换读写模式
- 可以自动扩容
- 支持链式调用,使用更流畅
- 很多地方体现零拷贝,例如 slice、duplicate、CompositeByteBuf