NIO系列(一) 核心概念介紹

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用法
  後續介紹網絡編程時詳解介紹。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章