1. 核心概念概述
近期又深入學習了一下NIO,預計寫幾篇NIO相關的博客作爲知識總結,博客內容主要爲筆者個人理解,如果錯誤望指正。
Java NIO是JDK1.4引入的新語法,與傳統IO不同,NIO是非阻塞且面向緩衝區(buffer)的編程。NIO有三個核心概念:Channel、Buffer與Select。
Channel: 表示IO源與目標(例如:文件、套接字)打開的連接。
Buffer: 表示緩衝區,應用程序都是通過buffer從channel中讀寫數據的,永遠不可能直接從channel讀寫數據。
Selector: 選擇器,是channel對象的多路複用器,可以監聽多個channel的IO狀態。應用程序可以通過selector獲取自己感興趣的IO事件,然後執行對應的業務邏輯。因此Selector多用在網絡編程中(例如多個client通過tcp連接一個server端),而本地文件讀寫通常用不到。
三者的關係如下:
2. Channel介紹
2.1作用
channel即管道,buffer中的數據可以通過channel進行傳輸,類似於傳統IO總的stream,與stream不同的是channel是雙向的。由於channel是雙向的,他能更好的反映出底層操作系統的真實狀況,因爲底層操作系統的通道就是雙向的。
2.2 主要實現類
實現類 | 概念 |
---|---|
FileChannel | 用於本地讀取、寫入、映射和操作文件的通道 |
DatagramChannel | 通過UDP讀寫網絡中的數據通道 |
SocketChannel | 通過TCP讀寫網絡中的數據通道 |
ServerSocketChannel | 可以監聽新進來的TCP連接,對每一個新進來的連接都會創建一個SocketChannel |
2.3 channel用法
FileInputStream inputStream = new FileInputStream("test.txt");
// 從stream中獲取channel
FileChannel channel = inputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(128);
// 通過channel,把文件的內容寫入buffer
channel.read(buffer);
3. Buffer介紹
3.1 作用
buffer即緩衝區,數據的載體,如:應用程序向目標(文件、套接字)寫入數據,那麼先將內容寫入buffer,buffer通過channel的傳輸寫入目標。若將channel比做高速公路,那麼buffer可以理解爲汽車。
3.2 實現類
Buffer本質上就是一塊內存,底層是通過數組實現的,通過數組存儲同一類型的數據,java針對主要基本類型都有具體的buffer實現(注意: boolean類型除外),如ByteBuffer、CharBuffer 、ShortBuffer 、IntBuffer 、LongBuffer 、FloatBuffer 、DoubleBuffer。
3.3 用法
下面以從文件讀取數據打印爲例,簡單說明buffer的用法。
FileInputStream inputStream = new FileInputStream("test.txt");
FileChannel channel = inputStream.getChannel();
// 創建buffer
ByteBuffer buffer = ByteBuffer.allocate(64);
// 從管道中把文件內容寫入buffer
channel.read(buffer);
// buffer讀入數據後,索引屬性發生變化,需要通過flip方法重置後才能讀取
buffer.flip();
// 把buffer內容打印出來
while (buffer.remaining() > 0) {
byte b = buffer.get();
System.out.println("charactre: " + (char) b);
}
inputStream.close();
3.4 基本屬性介紹
名稱 | 概念 |
---|---|
capacity | 表示Buffer最大的數據容量,緩衝區的容量不能爲負數,而且一旦創建,不可修改 |
limit | 緩衝區中當前的數據量,即位於limit之後的數據不可讀寫 |
position | 下一個要讀取或寫入的數據的索引 |
mark | 調用mark()方法來記錄一個特定的位置:mark=position,然後再調用reset()可以讓position恢復到標記的位置即position=mark |
下面通過圖示的方式,講解讀取過程中,各屬性的變化。當初始化一個buffer(容量爲64的)時,position=0,limit和capacity指向buffer數組結尾的後一個虛擬位置,屬性如下:
假設文件只有三個字節,全部讀入buffer後,position=3,position和capacity不變,如下:
此時如果想把buffer中的三個數據讀出來,必須調用flip方法,因爲position此時指向3,position只能向右移動,而右邊是沒有數據的,所以必須把position指向0,同時需要標明最大可以讀取到哪個位置,因此需要把limit指向3,此時屬性如下:
flip方法的源碼如下,和我們分析的一致,先將limit指向position,再將position指向0:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
mark屬性請看描述,這裏不再贅述。
4. Selector介紹
4.1 作用
selector是通道(SelectableChannel)的多路複用器,selector可以監聽多個已註冊通道的IO狀態,應用程序可以設置需要監聽的IO狀態類型。在傳統io中,通過soeket建立的連接,如果想實現監聽多個連接的IO狀態,那麼必須一個線程對應一個socket連接,當連接多了以後必定造成線程的增多,從而導致大量線程上下文切換的開銷。但是通過selector可以實現一個線程監聽多個連接,從而大大減少了線程的數量以及線程切換的開銷。
上面提到的SelectableChannel,是指一個支持selector進行狀態檢查的抽象類,在java api中,ServerSocketChannel、SocketChannel都繼承了該類,但是fileChannel這種不需要監聽狀態的channel沒有繼承。
selectableChannel在Selector中註冊的標識.每個Channel向Selector註冊時,都將會創建一個selectionKey,selectionKey 將Channel與Selector建立了關係,並維護了channel事件。
4.2 selector的創建
通過Selector的靜態方法open()進行創建
Selector selector = Selector.open();
通過源碼簡單介紹下創建過程,open()的源碼如下:
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
可以看出,首先獲取SelectorProvider,然後再獲取Selector。先看下SelectorProvider.provider()的邏輯,源碼如下:
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
此方法返回Java虛擬系統範圍默認的SelectorProvider,從方法註釋可以看到,首先如果系統定義了java.nio.channels.spi.SelectorProvider屬性,那麼直接通過反射實例化該類並返回,否則,返回sun.nio.ch.DefaultSelectorProvider.create(),create()方法返回WindowsSelectorProvider,由於我自己看的源碼不是openjdk,因此在類庫中根本就沒有sun.nio.ch這個包。因此,SelectorProvider.provider()方法返回的是WindowsSelectorProvider,WindowsSelectorProvider的openSelector方法返回的是WindowsSelectorImpl,需要下載openJdk才能看到源碼,源碼如下:
public class WindowsSelectorProvider extends SelectorProviderImpl {
public AbstractSelector openSelector() throws IOException {
return new WindowsSelectorImpl(this);
}
}
4.3 selector用法
後續介紹網絡編程時詳解介紹。