1、概述
傳統的Java io是面向流Stream的,阻塞IO;而nio則是面向緩衝區buffer的,非阻塞的io。區別是流中數據不能移動,而buffer可以移動。
Java nio選擇器Selectors允許單個線程監控多個通道的輸入和輸出。
Java緩衝區Buffer,本質上其實是一個內存塊,可以往裏面寫入數據,可以從裏面讀取數據。其被包裝成NIO buffer對象,便於操作。
Buffer 一般和Channel配合使用。
一般使用步驟如下:
1.數據寫入緩衝區,緩衝區會記錄寫入數據的大小。
2.調用flip方法,從寫入模式轉至讀取模式。
3.從緩衝區讀取數據
4.調用clear或者compact方法清除數據
2 Channel
2.1 channel與stram區別
channel 既可以讀也可以寫,而stream只能讀或者寫二選一。
channel可以異步讀寫
channel只能從buffer中進行讀寫
2.2 常見channel
FileChannel 從文件中讀取數據
DatagramChannel 從UDP中進行讀寫
SocketChannel 從TCP中進行讀寫
ServerSocketChannel 允許監聽進來的TCP連接,每個進來的連接都會創建一個SocketChannel
2.3用法
xxx.getChannel()方法獲取一個Channel,然後和buffer配合讀寫數據。
3 Buffer容量、位置、限制
capacity:容量,buffer的大小。創建時確定,無法更改。
position:位置,被讀取或寫入的下一個元素的位置。
寫模式:寫入數據的位置,默認爲0,最大可以是capacity-1
讀模式:從position開始讀取,然後position會自動增長,指向下一個讀取位置。
從寫模式切換到讀模式時,會將position重置爲0.
可以通過position(int)方法重設position
limit:限制,緩衝區中可以使用的元素個數。
寫模式:limit 等於capacity,表示你可以寫多少數據到緩衝區,l
讀模式:limit表示你可以從緩衝區讀取多少數據。
從寫模式切換到讀模式的時候,limit的值爲寫模式的position的值。
可以通過limit(int)方法重設limit
Mark 標記:
被記錄的位置,調用mark方法時設置(mark=position),默認爲-1;
buffer實際存儲的對象時數組,其是非線程安全的
4 NIO buffer類型
java nio緩衝區有以下幾種:
ByteBuffer
MappedByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
5 緩衝區的使用
5.1 創建
創建完成後,默認就是寫模式。
ByteBuffer.allocate(int size)
在JVM中分配size字節大小的容量。
ByteBuffer. allocateDirect(int size)
在JVM之外,系統內存中分配size字節大小的的容量。
XXXBuffer.wrap(byte[] array)
使用已經分配的數組作爲buffer管理。
5.2寫緩衝區
方式一:從通道Channel
裏面寫數據到緩衝區。
RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel fChannel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48);
//從fChannel中讀取數據寫入到buffer中,byteReads是返回的寫入數據的大小,-1表示寫完所有數據
int byteReads = fChannel.read(buffer);
方式二:使用buffer的putXXX方法
RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel fChannel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48);
buf.put(127);
put方法有很多重載方式,支持各種方式的寫入數據,具體可以查看Api。單參數的put方法會自動調整position的值,帶有index參數的put,如put(int index, byte b)
並導致任何屬性的變化。
5.3 flip
flip方法用於切換緩衝區從寫模式到讀模式。
調用flip 方法會講position設置爲0,同時將limit設置爲之前的position的值。
5.4 讀緩衝區
方式一:將數據從buffer讀取到channel
//從buffer讀取數據到channel,返回值byteReads爲讀取的數據大小,-1表示讀取完所有數據。
byteReads = fChannel.read(buffer);
方式二:使用getXXX方法讀取數據
getXXX方法同put方法類似
buf.get()
可以使用rewind()
方法,將position設置爲0,然後重新從buffer讀取數據。get()
會導致position的變化,get(int index)
不會導致任何屬性的變化。
5.5 數據清除
讀取完數據後,若需要再次寫入,可以調用clear
或compact
方法清除數據
clear
方法將position設置爲0,將limit設置爲capacity,實際並沒有清空數據,只是重置了寫入的位置。
compact
方法將未讀取的數據移動到緩衝區開始位置,將position設置在最後的未讀元素後面,limit仍然設置爲capacity。compact使得buffer可以再次寫入,append到之前未讀數據後面。
5.6 設置標記
mark()
方法可以在buffer中設置一個標記,記錄當前position,然後若想回到這個標記的位置調用reset()
即可。
5.7 buffer之間的比較
equals()
比較buffer中剩餘部分元素,其判斷相等的條件:
1、兩個buffer有數據相同的類型
2、buffer剩餘元素的數目相等
3、buffer剩餘元素一一相等
compareTo()
:比較兩個buffer的剩餘元素,bufferA比bufferB小的情況如下:
1、bufferA的元素小於bufferB中對應位置的元素
2、所有元素相等,但bufferA比bufferB元素個數少。
5.8 order方法
獲取以及設置讀寫的字節序。
6 Scatter/Gather
Java NIO支持scatter/gather(分散/聚集),用於描述從通道讀取和寫入通道。使用場景:消息數據各部分需要分開傳輸,比如消息頭和消息體組成的消息。
分散讀是在讀操作的時候,讀取的數據被寫入多個buffer中。scatter將數據從通道寫入多個buffer。
聚集寫,在寫入多個buffer到一個通道,此時gather將數據從多個buffer寫入通道。
6.1 read
read 把通道里的數據按照buffers數組中的順序填充到相應的buffer中。當第一個buffer填充滿後,纔會填充下一個buffer。
ByteBuffer head = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {head, body};
channel.read(buffers);
6.2 write
write方法按照數組中的順序,一個個的講buffer中的內容寫入Channel中。而且寫入的時候是僅僅將buffer的position和limit之間的元素寫入。而不是全部寫入。
ByteBuffer head = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {head, body};
channel.write(buffers);