天天看點

NIO學習筆記(一)入門ByteBuffer類NIO學習筆記(一)入門ByteBuffer類

NIO學習筆記(一)入門ByteBuffer類

ByteBuffer類是Buffer類的子類,可以在緩沖區中以位元組為機關對資料進行存取,而且他也是比較常用和重要的緩沖區類。在使用NIO技術時,有很大的機率使用ByteBuffer類來進行資料處理。

ByteBuffer類提供6類操作:

  1. 以絕對位置和相對位置讀寫單個字元的get()和put()方法。
  2. 使用相對批量get(byte[ ] dst)方法可以将緩沖區中的連續位元組傳輸到byte[ ] dst目标數組中
  3. 使用相對批量put(byte[ ] dst)方法可以将byte[ ] dst目标數組或其他位元組緩沖區中的連續位元組傳輸到此緩沖區中
  4. 使用絕對和相對getType和putType方法可以按照位元組順序在位元組序列中讀寫其他基本類型的值,方法getType和putType可以進行資料類型的自動轉換
  5. 提供了建立視圖緩沖區的方法,這些方法允許将自己餓緩沖區視為包含其他基本資料類型值得緩沖區,這些方法有asCharBuffer()、asDoubleBuffer()、asIntBuffer()、asLongBuffer()和asShortBuffer()
  6. 提供了對位元組緩沖區進行壓縮(compacting)、複制(duplicating)和截取(slicing)的方法

1、ByteBuffer緩沖區的直接與非直接緩沖區

ByteBuffer分為兩類,一個是分為兩類直接位元組緩沖區,另一個是非直接位元組緩沖區。

如果位元組緩沖區為直接位元組緩沖區,則JVM會盡量在直接位元組緩沖區上執行本機I/O操作,也就是直接對核心空間進行通路,以提高運作效率。提高運作效率的原理就是在每次調用基于作業系統的I/O操作之前和之後,JVM都會盡量避免将緩沖區的内容複制到中間緩沖區,或者從中間緩沖區複制内容,這樣就節省了一個步驟。

那麼非直接緩沖區是什麼樣的呢?當我們通過ByteBuffer向硬碟存取資料時是需要将資料暫存在JVM的中間緩沖區,如果有頻繁操作資料的情況發生,則每次操作時都會都會将資料暫存在JVM的中間緩沖區,再交給ByteBuffer處理,這樣做大大降低了軟體對資料的吞吐量,提高了記憶體占有率,造成了軟體運作效率低下。這就是非直接緩沖區。

2、建立ByteBuffer緩沖區

因為緩沖區分為兩類,是以這裡我一一介紹,如何建立這兩種緩沖區。

首先ByteBuffer是被abstract修飾的,是以不可以使用new來建立執行個體。這裡ByteBuffer提供了工廠方法allocateDirect()和allcate()。(英文好的可能一眼就知道這個方法是用來幹嘛的,allocate是配置設定的意思,direct是直接的意思)我們可以通過這個方法建立位元組緩沖區。通過工廠方法allocateDirect()傳回的緩沖區進行記憶體的配置設定和釋放所需要的時間成本通常都要高于非直接緩沖區。直接緩沖區操作的資料不在JVM堆中,而是在核心空間中,根據這個可以分析出,直接緩沖區善于儲存那些易受作業系統本機I/O影響的大量、長時間儲存的資料。

allocateDirect(int capacity)方法的作用是:配置設定一個新的直接位元組緩沖區。

allocate(int capacity)方法的作用是:配置設定一個新的非直接位元組緩沖區。

當然除了allocate我們還可以使用wrap()這個方法來建立緩沖區。當然wrap方法建立出來的也是非直接位元組緩沖區。

具體建立示例如下:

public class Niodemo {
    public static void main(String[] args) {
        //配置設定可以容納100個位元組的直接位元組緩沖區
        ByteBuffer buffer1 = ByteBuffer.allocateDirect(100);
        //配置設定可以容納100個位元組的非直接位元組緩沖區
        ByteBuffer buffer2 = ByteBuffer.allocate(100);

        byte[] bytes = new byte[100];
        //将bytes這個位元組類型數組,包裝成位元組緩沖區,容量依舊為100
        ByteBuffer buffer3 = ByteBuffer.wrap(bytes);
    }
}
           

在JDK中,可以檢視一下allocate()的源碼,從中你會發現其中建立一個新的數組,而wrap()方法是使用傳入的數組作為存儲空間,說明對wrap()關聯的數組進行操作會影響到緩沖區的資料,而操作緩沖區中的資料也會影響到與wrap()關聯的數組中的資料,原理其實就是因為引用了同一個數組對象。

那麼使用allocateDirect()方法建立的直接緩沖區如何釋放記憶體?有兩種方法,一種是手動釋放,另一種其實是交給JVM進行處理。先來看第一種手動釋放:

public class ReleaseDemo {
    public static void main(String[] args) throws SecurityException,
            InterruptedException, NoSuchMethodException,
            InvocationTargetException, IllegalAccessException {
        //A階段:建立直接緩沖區,容量int的最大值2147483647
        System.out.println("A");
        ByteBuffer buffer = ByteBuffer.allocateDirect(Integer.MAX_VALUE);
        //B階段:将建立的直接緩沖區填充1
        System.out.println("B");
        byte[] byteArray = new byte[]{1};
        System.out.println(Integer.MAX_VALUE);
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            buffer.put(byteArray);
        }
        //填充結束
        System.out.println("put end !");
        //休眠1s
        Thread.sleep(1000);
        //使用反射調用DirectByteBuffer的cleaner()方法,傳回一個Cleaner類
        Method cleanerMethod = buffer.getClass().getMethod("cleaner");
        cleanerMethod.setAccessible(true);
        Object returnValue = cleanerMethod.invoke(buffer);
        //通過反射調用Cleaner類的clean方法,釋放記憶體
        Method cleanMethod = returnValue.getClass().getMethod("clean");
        cleanMethod.setAccessible(true);
        cleanMethod.invoke(returnValue);
    }
}
           

3、wrap包裝資料的處理

之前已經知道了,wrap(byte[ ] array)這個方法的作用了。他是将byte數組包裝到緩沖區中。新的緩沖區将由給定的byte數組支援,也就是說,緩沖區如果修改了資料,那麼會導緻原數組的資料也被修改。新的緩沖區的capacity(容量)和limit(讀取限制)都将為array.length,其讀取位置position為0,其标記mark為undefined,也就是未定義。其底層實作的數組為給定的數組,arrayOffset為0.

當然wrap還有另外一種實作。wrap(byte[ ] array, int offset , int length),它的作用是将byte數組包裝到緩沖區中。新的緩沖區将由給定的byte數組支援,也就是說,緩沖區修改則導緻原數組修改。新緩沖區的容量為array.length,他的position為offset,其limit為offset+length,其标記mark依然為undefined。其底層實作的數組為給定的數組,arrayOffset為0.

注意:wrap(byte[ ] array, int offset , int length)并不具有subString()的截取作用,他的參數offset隻是設定position的值,而length确定limit的值
public class WrapDemo {
    public static void main(String[] args) {
        byte[] byteArray = new byte[]{1,2,3,4,5,6,7,8};
        ByteBuffer buffer1 = ByteBuffer.wrap(byteArray);
        ByteBuffer buffer2 = ByteBuffer.wrap(byteArray , 2 , 4);
        System.out.println("buffer1 capacity = " + buffer1.capacity() +
                " limit="+buffer1.limit()+" position="+buffer1.position());
        System.out.println(" ");
        System.out.println("buffer2 capacity = " + buffer2.capacity() +
                " limit="+buffer2.limit()+" position="+buffer2.position());
    }
}
           
## 運作結果
buffer1 capacity = 8 limit=8 position=0
 
buffer2 capacity = 8 limit=6 position=2