Java NIO之Buffer

奇舞移動發表於2018-12-07

什麼是NIO

在計算機世界,要想和計算機互動必須有輸入(Input)和輸出(Output)才能把我們的請求傳送給計算機,計算機處理後給我們一個結果,可見輸入輸出很重要。同理在Java中也有處理輸入輸出的模組。包括IO和NIO,其中IO相關程式碼在java.io包下,我們今天要聊的NIO在java.nio包下。這兩種IO有什麼區別呢?

傳統IO基於位元組流和字元流進行操作,而NIO基於Channel和Buffer進行操作。IO的流是阻塞的,當一個執行緒呼叫read() 或 write()時,該執行緒被阻塞。NIO的非阻塞模式,使一個執行緒從某通道傳送請求讀取資料,但是它僅能得到目前可用的資料,如果目前沒有資料可用時,就什麼都不會獲取,該執行緒可以做其他的事情。

我們今天要聊的Buffer是NIO中一個非常重要的類。Buffer是一個抽象類,它有以下子類:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

我們就以ByteBuffer為例,詳細瞭解一下Buffer的用法。

初始化

ByteBuffer同樣是一個抽象類,我們通過allocate方法,最終建立的是HeapByteBuffer物件,關於ByteBuffer的使用,我們重點關注以下幾個值的變化。

capacitypositionlimitremaining

分配10位元組空間大小

ByteBuffer buffer = ByteBuffer.allocate(10);
複製程式碼

allocate最終呼叫的方法是:

public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
}
複製程式碼

分配10個位元組的空間之後,列印下我們關注的值的初始狀態。

System.out.format("positon:%d remaining:%d limit:%d capacity:%d\n", buffer.position(), buffer.remaining(), buffer.limit(), buffer.capacity());
複製程式碼

https://user-gold-cdn.xitu.io/2018/12/7/1678650302ce1188?w=996&h=222&f=png&s=16474

如圖,分配好10位元組空間後,值變化如下。

positon:0 remaining:10 limit:10 capacity:10
複製程式碼

capacity表示容量的大小,為初始化是傳入的值的大小,之後便不會變化。positon指向即將要操作的位置。在寫狀態下limit表示可寫的空間的大小。remaining表示剩餘可寫空間的大小。

put

接下來我們呼叫Buffer的put方法,給我們建立的buffer放入一些東西。然後再列印下我們關注的值的變化,put的程式碼如下。

String str = "ABC";
byte[] bytes = str.getBytes();
buffer.put(bytes);
複製程式碼

https://user-gold-cdn.xitu.io/2018/12/7/1678650302af59d5?w=866&h=198&f=png&s=17069

positon:3 remaining:7 limit:10 capacity:10
複製程式碼

列印結果如上,put完3個位元組之後,position變成了3,remaining變成了7,其餘兩個值不變。

flip

put完之後,我們嘗試從buffer中讀一些資料,flip方法是將寫模式變成讀模式,它的實現如下。

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}
複製程式碼

可以看到它把position的值變成了0,把position的值賦給了limit,表示從起始位置開始讀,來看一下呼叫之後值的變化。

buffer.flip();
複製程式碼

https://user-gold-cdn.xitu.io/2018/12/7/16786503029b6210?w=958&h=198&f=png&s=17680

positon:0 remaining:3 limit:3 capacity:10
複製程式碼

列印的結果如上,position變成了0,limit變成了原來position的值,也就是3。remaining也為3,capacity不變。

get

上面已經切換到讀模式了,我們來讀一個位元組試試。

Byte byte1 = buffer.get();
複製程式碼

https://user-gold-cdn.xitu.io/2018/12/7/1678650302db3230?w=902&h=184&f=png&s=16744

positon:1 remaining:2 limit:3 capacity:10
複製程式碼

讀完一個位元組之後列印結果如上。

mark

這裡呼叫一下mark,mark之後不會有變化,只是會把position的值賦值給mark,我們看下它的實現程式碼。注意。此時mark的值變成了1,後邊會用到這個值。

public final Buffer mark() {
    mark = position;
    return this;
}
複製程式碼

然後呼叫一下,看列印結果。

buffer.mark();
複製程式碼

https://user-gold-cdn.xitu.io/2018/12/7/1678650302db3230?w=902&h=184&f=png&s=16744

positon:1 remaining:2 limit:3 capacity:10
複製程式碼

列印結果如上:幾個值沒有變化,符合預期。

再次get

接下來,我們再次呼叫一下get

Byte byte2 = buffer.get();
複製程式碼

https://user-gold-cdn.xitu.io/2018/12/7/1678650302bbf33f?w=858&h=184&f=png&s=16408

positon:2 remaining:1 limit:3 capacity:10
複製程式碼

列印結果如上,參考第一次get,應該比較好理解。

reset

還記得前面,我們呼叫mark,把position的值賦值給mark。這次我們來呼叫reset,它的作用是把之前mark的值重新賦值給position。它的實現如下:

public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}
複製程式碼

https://user-gold-cdn.xitu.io/2018/12/7/1678650302db3230?w=902&h=184&f=png&s=16744

positon:1 remaining:2 limit:3 capacity:10
複製程式碼

列印結果如上,可以看到我們又回到了之前的狀態,因此可以通過mark和reset反覆讀取buffer裡的內容。

第三次get

由於我們之前呼叫了reset,現在第三次調一下get,看看是不是符合我們的預期。

Byte byte3 = buffer.get();
複製程式碼

https://user-gold-cdn.xitu.io/2018/12/7/1678650302bbf33f?w=858&h=184&f=png&s=16408

positon:2 remaining:1 limit:3 capacity:10
複製程式碼

列印結果如上,符合預期。

rewind

在讀了一些資料之後,如果我們想重新讀怎麼辦?可以用rewind,它會把position的值置為0,同時mark值恢復為-1。

public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}
複製程式碼

https://user-gold-cdn.xitu.io/2018/12/7/16786503029b6210?w=958&h=198&f=png&s=17680

positon:0 remaining:3 limit:3 capacity:10
複製程式碼

列印結果如上。

clear

最後我們來看一下clear的用法,clear會把position、limit、capacity恢復到初始狀態,它的實現如下:

public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}   
複製程式碼

https://user-gold-cdn.xitu.io/2018/12/7/1678650302ce1188?w=996&h=222&f=png&s=16474

positon:0 remaining:10 limit:10 capacity:10
複製程式碼

列印結果如上,可以看到,又回到了初始狀態。

總結

以上就是關於Buffer的基本用法,在接下來的文章中會陸續介紹NIO的其他類的用法,歡迎持續關注。

最後推薦一個線上畫圖的網站draw.io可以儲存到本地或github等多平臺。

本文demo

關注微信公眾號,最新技術乾貨實時推送

image

相關文章