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