亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

首頁 慕課教程 Netty 教程 Netty 教程 Netty ByteBuf幾種類型

Netty ByteBuf 幾種類型

1. 前言

上一節,我們主要學習了 ByteBuf 的核心 API,相信大家都能掌握,本節主要介紹 ByteBuf 的幾種分類。

2. 創建一個 ByteBuf

常見創建 ByteBuf 主要有兩種方式,分別如下所示:

方式一:

ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);

方式二:

ByteBuf byteBuf = Unpooled.buffer(10);

思考:那么這兩種方式有什么關聯呢?

Unpooled.buffer 源碼,以下代碼是經過整理,只保留了核心代碼。

public final class Unpooled {
    private static final ByteBufAllocator ALLOC;
    
    static {
        ALLOC = UnpooledByteBufAllocator.DEFAULT;
    }
    
    public static ByteBuf buffer(int initialCapacity) {
        return ALLOC.heapBuffer(initialCapacity);
    }
    
    public static ByteBuf directBuffer() {
        return ALLOC.directBuffer();
    }
}

通過源碼,我們可以知道,其實 Unpooled 工具類也是調用 ByteBufAllocator 去創建 ByteBuf 的。從字面上我們能夠大概猜到它針對的是非池化的 ByteBuf 進行創建的。

3. ByteBuf 分類

ByteBuf 是一個字節容器,底層是根據容量值來申請一塊內存區域來存儲字節數組的。既然涉及到內存,那么會分為直接內存和 JVM 內存,這個和 NIO 的直接緩沖器和非直接緩沖器是一樣的道理。直接內存,速度很快,垃圾回收是不受 JVM 控制,容易造成內存爆滿。

ByteBuf 主要分為三種類型

  1. Pooled 和 Unpooled,池化和非池化;
  2. Heap 和 Direct,堆內存和直接內存;
  3. Safe 和 Unsafe,安全和非安全。

池化和非池化: 池化就是用完就放回池子里面,比如我們所熟悉的數據庫連接池。非池化就是每次使用都重新創建,使用完成則立馬銷毀。從性能的角度來說,池化會比非池化相對高,因為可以重復利用,避免每次都重新創建。

堆內存和直接內存: 堆內存是 JVM 內部開辟的一塊內存空間,它的生命周期受到 JVM 來管理,不容易造成內存溢出的情況。直接內存則是直接受操作系統管理了,如果數據量很大的情況,容易造成內存溢出情況。

安全和非安全: 主要是 Java 操作底層操作數據的一種安全和非安全的方式。

圖片描述

根據不同類型進行組合,得到常見 ByteBuf 的實現類

  1. 池化 + 堆內存,PooledHeapByteBuf;
  2. 池化 + 直接內存,PooledDirectByteBuf;
  3. 池化 + 堆內存 + 不安全,PooledUnsafeHeapByteBuf;
  4. 池化 + 直接內存 + 不安全,PooledUnsafeDirectByteBuf;
  5. 非池化 + 堆內存,UnpooledHeapByteBuf;
  6. 非池化 + 直接內存,UnpooledDirectByteBuf;
  7. 非池化 + 堆內存 + 不安全,UnpooledUnsafeHeapByteBuf;
  8. 非池化 + 直接內存 + 不安全,UnpooledUnsafeDirectByteBuf。

4. ByteBufAllocator 的使用

由于 ByteBuf 的組合種類非常的多,如果讓用戶手工去創建的化,會非常的麻煩,并且對每種類型不熟悉,很容易出現性能問題。這點跟 Java 線程池有點類似,線程池的種類分好幾種,但是通常都是通過 Executors 工具類來進行線程池的創建。

其中,ByteBufAllocator 又主要分為兩種,分別是 UnpooledByteBufAllocatorPooledByteBufAllocator。其實,一般情況下我們不需要直接使用具體的分配器,而是使用它默認的即可。

4.1 默認分配 - 池化 & 非池化

實例:

ByteBufAllocator byteBufAllocator = ByteBufAllocator.DEFAULT;

源碼:

public interface ByteBufAllocator {
    ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;
}

源碼:以下源碼是經過處理,只保留核心部分。

public final class ByteBufUtil {
    static final ByteBufAllocator DEFAULT_ALLOCATOR;
    static {
        //1.分配類型
        String allocType = SystemPropertyUtil.get("io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
        
        //2.根據類型,創建不同的分配器
        Object alloc;
        if ("unpooled".equals(allocType)) {
            alloc = UnpooledByteBufAllocator.DEFAULT;
            
        } else if ("pooled".equals(allocType)) {
            alloc = PooledByteBufAllocator.DEFAULT;
            
        } else {
            alloc = PooledByteBufAllocator.DEFAULT;
        }

        DEFAULT_ALLOCATOR = (ByteBufAllocator)alloc;
    }
}

根據以上的源碼,我們可以知道,使用 ByteBufAlloctor 來創建 ByteBuf 時,會判斷使用池化還是非池化的分配器。

4.2 默認分配 - 堆內存 & 直接內存

實例:

ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);

源碼:以下源碼是經過處理,只保留核心部分。

public abstract class AbstractByteBufAllocator implements ByteBufAllocator {
    private final boolean directByDefault;
    
    //構造函數
    protected AbstractByteBufAllocator(boolean preferDirect) {
        this.directByDefault = preferDirect && PlatformDependent.hasUnsafe();
    }
    
    public ByteBuf buffer(int initialCapacity) {
        return this.directByDefault ? this.directBuffer(initialCapacity) : this.heapBuffer(initialCapacity);
    }
}

通過 directByDefault 來判斷是否選擇創建堆內存還是直接內存的 ByteBuf,而 directByDefault 是在構造函數里面進行傳值的,那么它是一個抽象類,因此肯定是從其子類的構造函數傳值進來。

繼續查看源碼:

public class PooledByteBufAllocator extends AbstractByteBufAllocator {
    public PooledByteBufAllocator() {
        //傳遞的是false
        this(false);
    }
}

public final class UnpooledByteBufAllocator extends AbstractByteBufAllocator {
    public UnpooledByteBufAllocator(boolean preferDirect) {
        this(preferDirect, false);
    }

    public UnpooledByteBufAllocator(boolean preferDirect, boolean disableLeakDetector) {
        //傳遞值給父類的構造函數
        super(preferDirect);
        this.disableLeakDetector = disableLeakDetector;
    }
}

總結,ByteBufAllocator 的核心兩個步驟分別如下:

  1. 確定是使用哪個分配器,池化還是非池化?UnpooledByteBufAllocatorPooledByteBufAllocator
  2. 再確定是堆內存還是直接內存,主要是在 UnpooledByteBufAllocatorPooledByteBufAllocator 的構造函數里面傳值確定。

4.3 核心方法

方式 描述
buffer(); 創建 ByteBuf(堆內存還是直接內存?),默認容量值
buffer(int var1); 創建 ByteBuf(堆內存還是直接內存?),手工指定容量值
buffer(int var1, int var2); 創建 ByteBuf(堆內存還是直接內存?),手工指定容量值和最大容量值
heapBuffer(); 創建一個堆內存的 ByteBuf,默認容量值
heapBuffer(int var1); 創建一個堆內存的 ByteBuf,手工指定容量值
heapBuffer(int var1, int var2); 創建一個堆內存的 ByteBuf,手工指定容量值和最大容量值
directBuffer(); 創建一個直接內存的 ByteBuf,默認容量值
directBuffer(int var1); 創建一個直接內存的 ByteBuf,手工指定容量值
directBuffer(int var1, int var2); 創建一個直接內存的 ByteBuf,手工指定容量值和最大容量值

一般推薦使用 buffer ()、buffer(int var1)、buffer(int var1,int var2),因為 Netty 底層回去幫選擇創建最優的 ByteBuf。

5. Unpooled 的使用

Unpooled 主要是使用了非池化技術,可以創建堆內存和直接內存的 ByteBuf。

核心 API 如下所示:

方法 描述
ByteBuf buffer() 創建非池化 + 堆內存的 ByteBuf,默認容量大小
ByteBuf buffer(int initialCapacity) 創建非池化 + 堆內存的 ByteBuf,并且可以指定容量大小
ByteBuf directBuffer() 創建非池化 + 直接內存的 ByteBuf,默認容量大小
directBuffer(int initialCapacity) 創建非池化 + 直接內存的 ByteBuf,并且可以指定容量大小
ByteBuf copiedBuffer(byte[] array) 創建非池化 + 堆內存的 ByteBuf,并且初始化字節數組
ByteBuf copiedBuffer(byte[] array, int offset, int length) 創建非池化 + 堆內存的 ByteBuf,并且把字節數組的部分內容初始化到 ByteBuf
ByteBuf copiedBuffer(ByteBuf buffer) 創建非池化 + 堆內存的 ByteBuf,并且把參數的 ByteBuf 寫入到新創建的 ByteBuf 里

以上的方法是平時我們使用 Unpooled 時使用最多的,難度不大,只需要分清每個方法的作用是什么即可,可以根據自己需求選擇合適的 ByteBuf 類型。

6. 小結

本節主要介紹了 ByteBuf 的幾種核心類型以及創建 ByteBuf 的幾種方式

  1. 掌握 ByteBuf 的三種類型,分別是池化與非池化、堆內存與直接內存、安全與不安全,以及它們之間的含義;
  2. ByteBufAllocator 分配器,它是如何去創建 ByteBuf 的,幾種模式;
  3. Unpooled,Netty 提供的非池化工具類。