轉載請註明原創出處,謝謝!
說在前面
NIO相關知識是很多後續的一些基礎知識,所以今天這篇文章僅僅是簡單介紹,後續會繼續有一到二篇相關NIO內容。
什麼是NIO
Java NIO( New IO) 是從Java 1.4版本開始引入的一個新的IO API,可以替代標準的Java IO API。NIO與原來的IO有同樣的作用和目的,但是使用的方式完全不同, NIO支援面向緩衝區的、基於通道的IO操作。 NIO將以更加高效的方式進行檔案的讀寫操作。
NIO與普通IO的主要區別
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向緩衝區(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
(無) | 選擇器(Selectors) |
- Channels and Buffers(通道和緩衝區):標準的IO基於位元組流和字元流進行操作的,而NIO是基於通道(Channel)和緩衝區(Buffer)進行操作,資料總是從通道讀取到緩衝區中,或者從緩衝區寫入到通道中。
- Asynchronous IO(非同步IO):Java NIO可以讓你非同步的使用IO,例如:當執行緒從通道讀取資料到緩衝區時,執行緒還是可以進行其他事情。當資料被寫入到緩衝區時,執行緒可以繼續處理它。從緩衝區寫入通道也類似。
- Selectors(選擇器):Java NIO引入了選擇器的概念,選擇器用於監聽多個通道的事件(比如:連線開啟,資料到達)。因此,單個的執行緒可以監聽多個資料通道。
**說明:**Asynchronous IO(非同步IO)、Selectors(選擇器)等後續文章會繼續介紹的。
緩衝區(Buffer)
通過上面NIO與普通IO的主要區別也可以看到在基本的IO操作中所有的操作都是基於流進行操作的,而在NIO中所有的操作都是基於緩衝區繼續操作的,所有的讀寫操作都是通過快取區來進行完成,緩衝區(Buffer)是一個線性的、有序的資料集,只能容納特定的資料型別(基本就是基本資料型別對應的Buffer或者起子類)。
各各資料型別的快取區類
快取區類 | 相關描述 |
---|---|
ByteBuffer | 儲存位元組的Buffer |
CharBuffer | 儲存字元的Buffer |
ShortBuffer | 儲存短整型的Buffer |
IntBuffer | 儲存整型的Buffer |
LongBuffer | 儲存長整型的Buffer |
FloatBuffer | 儲存單精度浮點型Buffer |
DoubleBuffer | 儲存雙精度浮點型Buffer |
**備註:**看到上面這幾類是不是想起了JAVA的8種基本資料型別,唯一缺少boolean對於的型別。
第一問:為什麼boolean不需要快取呢? 可以查閱之前寫的:java二進位制相關基礎,裡面有描述規範中數字的內部表示和儲存,boolean所佔位數1bit(取值只有true或者false),由於位元組(byte)是作業系統和所有I/O裝置使用的基本資料型別,所以基本都是以位元組或者連續的一段位元組來儲存表示,所以就沒有boolean,感覺也沒有必要boolean型別的快取操作(像RocketMQ原始碼裡面可能把一個Int裡面的某位來表示boolean,其他位繼續來儲存資料,歡迎關注我的公眾號【匠心零度】,後續RocketMQ原始碼類分析的時候如何運用上述技巧進行說明等,其實上面我寫的好幾篇文章都是為了後續RocketMQ原始碼分析做準備的)。
Buffer使用
讀資料:
- flip()方法
- 將Buffer從寫模式切換到讀模式
- 呼叫flip()方法會將position設回0,並將limit設定成之前position的值。
- buf.flip();
- buf.get()
- 讀取資料
- Buffer.rewind()
- 將position設回0,所以你可以重讀Buffer中的所有資料
- limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)
- Buffer.mark()方法,可以標記Buffer中的一個特定position。之後可以通過呼叫。
- Buffer.reset()方法,恢復到Buffer.mark()標記時的position。
- clear()方法會:
- 清空整個緩衝區。
- position將被設回0,limit被設定成 capacity的值
- compact()方法:
- 只會清除已經讀過的資料;任何未讀的資料都被移到緩衝區的起始處,新寫入的資料將放到緩衝區未讀資料的後面。
- 將position設到最後一個未讀元素正後面,limit被設定成 capacity的值。
寫資料: buf.put(127);
緩衝區的基本屬性
- 容量 (capacity):表示 Buffer 最大資料容量,緩衝區容量不能為負,並且建立後不能更改。
- 限制 (limit):第一個不應該讀取或寫入的資料的索引,即位於 limit 後的資料不可讀寫。緩衝區的限制不能為負,並且不能大於其容量。
- 位置 (position):下一個要讀取或寫入的資料的索引。緩衝區的位置不能為負,並且不能大於其限制。
**備註:**標記、 位置、 限制、 容量遵守以下不變式: 0 <= position <= limit <= capacity。
為了更形象解釋上面重要屬性,準備配上簡單程式碼以及圖來進行說明就容易懂了。
//第一步,獲取IntBuffer,通過IntBuffer.allocate操作
IntBuffer buf = IntBuffer.allocate(10) ; // 準備出10個大小的緩衝區
//第二步未操作前輸出屬性值
System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;
//第三步進行設定資料
buf.put(6) ; // 設定一個資料
buf.put(16) ; // 設定二個資料
//第四步操作後輸出屬性值
System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;
//第五步將Buffer從寫模式切換到讀模式 postion = 0 ,limit = 原本position
buf.flip() ;
//第六步操作後輸出屬性值
System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;
複製程式碼
程式輸出結果:
position = 0,limit = 10,capacty = 10
position = 2,limit = 10,capacty = 10
position = 0,limit = 2,capacty = 10
複製程式碼
檢視下圖來進行說明:
通道(Channel)
通道表示開啟到 IO 裝置(例如:檔案、套接字)的連線。若需要使用 NIO 系統,需要獲取用於連線 IO 裝置的通道以及用於容納資料的緩衝區。然後操作緩衝區,對資料進行處理。Channel 負責傳輸, Buffer 負責儲存。通道是由 java.nio.channels 包定義的。 Channel 表示 IO 源與目標開啟的連線。Channel 類似於傳統的“流”。只不過 Channel本身不能直接訪問資料, Channel 只能與Buffer 進行互動。
通道都是操作快取區完成全部的功能的。
Java中所有已知 Channel 實現類:
- AbstractInterruptibleChannel
- AbstractSelectableChannel
- DatagramChannel
- FileChannel
- Pipe.SinkChannel
- Pipe.SourceChannel
- SelectableChannel
- ServerSocketChannel
- SocketChannel
常用的有入下幾個:
- FileChannel:用於讀取、寫入、對映和操作檔案的通道。
- DatagramChannel:通過 UDP 讀寫網路中的資料通道。
- SocketChannel:通過 TCP 讀寫網路中的資料。
- ServerSocketChannel:可以監聽新進來的 TCP 連線,對每一個新進來的連線都會建立一個 SocketChannel。
獲取通道
獲取通道的一種方式是對支援通道的物件呼叫getChannel() 方法。支援通道的類如下:
- FileInputStream
- FileOutputStream
- RandomAccessFile
- DatagramSocket
- Socket
- ServerSocket
獲取通道的其他方式是使用 Files 類的靜態方法 newByteChannel() 獲取位元組通道。或者通過通道的靜態方法 open() 開啟並返回指定通道。
FileChannel
- 為了更形象解釋說明的Channel,下面準備以FileChannel的一些簡單程式碼進行說明就容易懂了。
- 準備以FileOutputStream類為準,這兩個類都是支援通道操作的。
String info[] = {"歡迎","關注","匠心零度","的","公眾號","謝謝!!"} ;
File file = new File("d:" + File.separator + "testfilechannel.txt") ;
FileOutputStream output = null ;
FileChannel fout = null;
try {
output = new FileOutputStream(file) ;
fout = null;
fout = output.getChannel() ; // 得到輸出的通道
ByteBuffer buf = ByteBuffer.allocate(1024) ;
for(int i=0;i<info.length;i++){
buf.put(info[i].getBytes()) ; // 字串變為位元組陣列放進緩衝區之中
}
buf.flip() ;
fout.write(buf) ; // 輸出緩衝區的內容
} catch (Exception e) {
e.printStackTrace();
}finally{
if(fout!=null){
try {
fout.close() ;
} catch (IOException e) {
e.printStackTrace();
}
}
if(output!=null){
try {
output.close() ;
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製程式碼
程式執行效果:
**說明:**今天只是NIO相關基礎篇一,所以有很多並沒有涉及到,希望上面說的這樣讓大家有一個新的瞭解,未完待續……
如果讀完覺得有收穫的話,歡迎點贊、關注、加公眾號【匠心零度】。
個人公眾號,歡迎關注,查閱更多精彩歷史!!!