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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章