什麼是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的使用,我們重點關注以下幾個值的變化。
capacity
、position
、limit
、remaining
分配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());
複製程式碼
如圖,分配好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);
複製程式碼
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();
複製程式碼
positon:0 remaining:3 limit:3 capacity:10
複製程式碼
列印的結果如上,position變成了0,limit變成了原來position的值,也就是3。remaining也為3,capacity不變。
get
上面已經切換到讀模式了,我們來讀一個位元組試試。
Byte byte1 = buffer.get();
複製程式碼
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();
複製程式碼
positon:1 remaining:2 limit:3 capacity:10
複製程式碼
列印結果如上:幾個值沒有變化,符合預期。
再次get
接下來,我們再次呼叫一下get
Byte byte2 = buffer.get();
複製程式碼
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;
}
複製程式碼
positon:1 remaining:2 limit:3 capacity:10
複製程式碼
列印結果如上,可以看到我們又回到了之前的狀態,因此可以通過mark和reset反覆讀取buffer裡的內容。
第三次get
由於我們之前呼叫了reset,現在第三次調一下get,看看是不是符合我們的預期。
Byte byte3 = buffer.get();
複製程式碼
positon:2 remaining:1 limit:3 capacity:10
複製程式碼
列印結果如上,符合預期。
rewind
在讀了一些資料之後,如果我們想重新讀怎麼辦?可以用rewind,它會把position的值置為0,同時mark值恢復為-1。
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
複製程式碼
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;
}
複製程式碼
positon:0 remaining:10 limit:10 capacity:10
複製程式碼
列印結果如上,可以看到,又回到了初始狀態。
總結
以上就是關於Buffer的基本用法,在接下來的文章中會陸續介紹NIO的其他類的用法,歡迎持續關注。
最後推薦一個線上畫圖的網站draw.io可以儲存到本地或github等多平臺。
本文demo