標籤:input leo 限制 sage 9.png 發送 分享 映射 ntb
一、概述
從JDK1.4開始,Java提供了一系列改進的輸入/輸出處理的新特性,被統稱為NIO(即New I/O)。新增了許多用於處理輸入輸出的類,這些類都被放在java.nio包及子包下,並且對原java.io包中的很多類進行改寫,新增了滿足NIO的功能。NIO採用記憶體對應檔的方式來處理輸入輸出,NIO將檔案或檔案的一段區域對應到記憶體中,這樣就可以像訪問記憶體一樣訪問檔案了。
NIO 與原來的 I/O 有同樣的作用和目的,但是它使用不同的方式? 塊I/O。塊 I/O 的效率可以比流 I/O 高許多。
流與塊的比較
原來的 I/O 庫(在 java.io.*中) 與 NIO 最重要的區別是資料打包和傳輸的方式。正如前面提到的,原來的 I/O 以流的方式處理資料,而 NIO 以塊的方式處理資料。
面向流 的 I/O 系統一次一個位元組地處理資料。一個輸入資料流產生一個位元組的資料,一個輸出資料流消費一個位元組的資料。為流式資料建立過濾器非常容易。連結幾個過濾器,以便每個過濾器只負責單個複雜處理機制的一部分,這樣也是相對簡單的。不利的一面是,面向流的 I/O 通常相當慢。
一個 面向塊 的 I/O 系統以塊的形式處理資料。每一個操作都在一步中產生或者消費一個資料區塊。按塊處理資料比按(流式的)位元組處理資料要快得多。但是面向塊的 I/O 缺少一些面向流的 I/O 所具有的優雅性和簡單性。
在NIO中有幾個核心對象需要掌握:緩衝區(Buffer)、通道(Channel)、選取器(Selector)。
二、緩衝區Buffer
緩衝區實際上是一個容器物件,更直接的說,其實就是一個數組,在NIO庫中,所有資料都是用緩衝區處理的。在讀取資料時,它是直接讀到緩衝區中的; 在寫入資料時,它也是寫入到緩衝區中的;任何時候訪問 NIO 中的資料,都是將它放到緩衝區中。而在面向流I/O系統中,所有資料都是直接寫入或者直接將資料讀取到Stream對象中。具體看下面這張圖就理解了:
上面的圖描述了從一個用戶端向服務端發送資料,然後服務端接收資料的過程。用戶端發送資料時,必須先將資料存入Buffer中,然後將Buffer中的內容寫入通道。服務端這邊接收資料必須通過Channel將資料讀入到Buffer中,然後再從Buffer中取出資料來處理。
在NIO中,所有的緩衝區類型都繼承於抽象類別Buffer,最常用的就是ByteBuffer,對於Java中的基本類型,基本都有一個具體Buffer類型與之相對應,它們之間的繼承關係如所示:
下面是一個簡單使用IntBuffer的例子:
package com.demo.nio;import java.nio.IntBuffer;public class TestIntBuffer { public static void main(String[] args) { // 分配新的int緩衝區,參數為緩衝區容量 // 新緩衝區的當前位置將為零,其界限(限制位置)將為其容量。它將具有一個底層實現數組,其數組位移量將為零。 IntBuffer buffer = IntBuffer.allocate(8); for (int i = 0; i < buffer.capacity(); ++i) { int j = 2 * (i + 1); // 將給定整數寫入此緩衝區的當前位置,當前位置遞增 buffer.put(j); } // 重設此緩衝區,將限制設定為當前位置,然後將當前位置設定為0 buffer.flip(); // 查看在當前位置和限制位置之間是否有元素 while (buffer.hasRemaining()) { // 讀取此緩衝區當前位置的整數,然後當前位置遞增 int j = buffer.get(); System.out.print(j + " "); } }}
運行後可以看到:
三、通道Channel
Channel和傳統IO中的Stream很相似。雖然很相似,但是有很大的區別,主要區別為:通道是雙向的,通過一個Channel既可以進行讀,也可以進行寫;而Stream只能進行單向操作,通過一個Stream只能進行讀或者寫,比如InputStream只能進行讀取操作,OutputStream只能進行寫操作;
通道是一個對象,通過它可以讀取和寫入資料,當然了所有資料都通過Buffer對象來處理。我們永遠不會將位元組直接寫入通道中,相反是將資料寫入包含一個或者多個位元組的緩衝區。同樣不會直接從通道中讀取位元組,而是將資料從通道讀入緩衝區,再從緩衝區擷取這個位元組。
在NIO中,提供了多種通道對象,而所有的通道對象都實現了Channel介面。它們之間的繼承關係如所示:
Channel(通道)表示到實體如硬體裝置、檔案、網路通訊端或可以執行一個或多個不同I/O操作的程式組件的開放的串連。所有的Channel都不是通過構造器建立的,而是通過傳統的節點InputStream、OutputStream的getChannel方法來返迴響應的Channel。
Channel中最常用的三個類方法就是map、read和write,其中map方法用於將Channel對應的部分或全部資料對應成ByteBuffer,而read或write方法有一系列的重載形式,這些方法用於從Buffer中讀取資料或向Buffer中寫入資料。
1、使用NIO讀取資料
在前面我們說過,任何時候讀取資料,都不是直接從通道讀取,而是從通道讀取到緩衝區。所以使用NIO讀取資料可以分為下面三個步驟:
(1). 從FileInputStream擷取Channel
(2). 建立Buffer
(3). 將資料從Channel讀取到Buffer中
下面是一個簡單的使用NIO從檔案中讀取資料的例子:
import java.io.*; import java.nio.*; import java.nio.channels.*; public class Program { public static void main( String args[] ) throws Exception { FileInputStream fin = new FileInputStream("c:\\test.txt"); // 擷取通道 FileChannel fc = fin.getChannel(); // 建立緩衝區 ByteBuffer buffer = ByteBuffer.allocate(1024); // 讀取資料到緩衝區 fc.read(buffer); buffer.flip(); while (buffer.remaining()>0) { byte b = buffer.get(); System.out.print(((char)b)); } fin.close(); } }
2、使用NIO寫入資料
使用NIO寫入資料與讀取資料的過程類似,同樣資料不是直接寫入通道,而是寫入緩衝區,可以分為下面三個步驟:
(1). 從FileInputStream擷取Channel
(2). 建立Buffer
(3). 將資料從Channel寫入到Buffer中
下面是一個簡單的使用NIO向檔案中寫入資料的例子:
package com.demo.nio;import java.io.FileOutputStream;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;public class Program { private static final byte message[] = { 83, 111, 109, 101, 32, 98, 121, 116, 101, 115, 46 }; public static void main( String args[] ) throws Exception { FileOutputStream fout = new FileOutputStream( "c:\\test.txt" ); FileChannel fc = fout.getChannel(); ByteBuffer buffer = ByteBuffer.allocate( 1024 ); for (int i=0; i<message.length; ++i) { buffer.put( message[i] ); } buffer.flip(); fc.write( buffer ); fout.close(); }}
四、選取器Selector
Selector類是NIO的核心類,Selector能夠檢測多個註冊的通道上是否有事件發生,如果有事件發生,便擷取事件然後針對每個事件進行相應的響應處理。這樣一來,只是用一個單線程就可以管理多個通道,也就是管理多個串連。這樣使得只有在串連真正有讀寫事件發生時,才會調用函數來進行讀寫,就大大地減少了系統開銷,並且不必為每個串連都建立一個線程,不用去維護多個線程,並且避免了多線程之間的環境切換導致的開銷。
與Selector有關的一個關鍵類是SelectionKey,一個SelectionKey表示一個到達的事件,這2個類構成了服務端處理業務的關鍵邏輯。
Java NIO2:NIO概述