這兩天花了時間學習了java的nio,看的書是Ron Hitchens著的 《Java NIO》,總的來說,這本書真的寫的非常好,而且整本書將java nio的內容從底層講了個遍,書不厚,但是確實值得一讀,這里總結一下學習后的一些心得。學習過程中既詳細看完了《Java NIO》這本書,同時也參照了http://zhangshixi.iteye.com/blog/679959該作者寫的關於NIO的博文。好了,我們正式開始NIO的學習吧。
首先,簡單說說I/O和NIO一些概念性的東西吧。
I/O流或者輸入/輸出流指的是計算機與外部世界或者一個程序與計算機的其余部分的之間的接口。新的輸入/輸出(NIO)庫是在JDK 1.4中引入的。NIO彌補了原來的I/O的不足,它在標准Java代碼中提供了高速的、面向塊的I/O。原來的I/O庫與NIO最重要的區別是數據打包和傳輸的方式的不同,原來的 I/O 以流 的方式處理數據,而 NIO 以塊 的方式處理數據。 面向流的I/O系統一次一個字節地處理數據。一個輸入流產生一個字節的數據,一個輸出流消費一個字節的數據。為流式數據創建過濾器非常容易。鏈接幾個過濾 器,以便每個過濾器只負責單個復雜處理機制的一部分,這樣也是相對簡單的。不利的一面是,面向流的I/O通常相當慢。
NIO與原來的I/O有同樣的作用和目的,但是它使用塊I/O的處理方式。每一個操作都在一步中產生或者消費一個數據塊。按塊處理數據比按(流式的)字節處理數據要快得多。但是面向塊的I/O缺少一些面向流的I/O所具有的優雅性和簡單性。
我們首先從一個例子開始NIO的學習
我們分別用I/O以及NIO來實現從一個文件中讀取內容並將其打印出來,看看I/O和NIO的特點。
使用IO來讀取指定文件中的前1024字節並打印出來:
/** * 使用IO讀取指定文件的前1024個字節的內容。 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { FileInputStream is = new FileInputStream("GitHub.txt"); byte[] buffer = new byte[1024]; is.read(buffer); System.out.println(new String(buffer)); is.close(); } /** * 使用NIO讀取指定文件的前1024個字節的內容。 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { FileInputStream is = new FileInputStream("GitHub.txt"); //為該文件輸入流生成唯一的文件通道 FileChannel FileChannel channel = is.getChannel(); //開辟一個長度為1024的字節緩沖區 ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); System.out.println(new String(buffer.array())); System.out.println(buffer.isDirect() + ", " + buffer.isReadOnly()); channel.close(); is.close(); }
從上面的例子中可以看出,NIO以通道Channel和緩沖區Buffer為基礎來實現面向塊的IO數據處理。下面將討論並學習NIO 庫的核心概念以及從高級的特性到底層編程細節的幾乎所有方面。
NIO中的讀和寫
1) 概述:
讀和寫是I/O的基本過程。從一個通道中讀取很簡單:只需創建一個緩沖區,然后讓通道將數據讀到這個緩沖區中。寫入也相當簡單:創建一個緩沖區,用數據填充它,然后讓通 道用這些數據來執行寫入操作。
2) 從文件中讀取:
如果使用原來的I/O,那么我們只需創建一個FileInputStream並從它那里讀取。而在NIO中,情況稍有不同:我們首先從FileInputStream獲取一個FileChannel對象,然后使用這個通道來讀取數據。
在NIO系統中,任何時候執行一個讀操作,您都是從通道中讀取,但是您不是直接從通道讀取。因為所有數據最終都駐留在緩沖區中,所以您是從通道讀到緩沖區中。
因此讀取文件涉及三個步驟:
(1) 從FileInputStream獲取Channel。
(2) 創建Buffer。
(3) 將數據從Channel讀到Buffer 中。
現在,讓我們看一下這個過程。
// 第一步是獲取通道。我們從 FileInputStream 獲取通道: FileInputStream fin = new FileInputStream( "readandshow.txt" ); FileChannel fc = fin.getChannel(); // 下一步是創建緩沖區: ByteBuffer buffer = ByteBuffer.allocate( 1024 ); // 最后,需要將數據從通道讀到緩沖區中: fc.read( buffer );
您會注意到,我們不需要告訴通道要讀多少數據到緩沖區中。每一個緩沖區都有復雜的內部統計機制,它會跟蹤已經讀了多少數據以及還有多少空間可以容納更多的數據。我們將在緩沖區內部細節中介紹更多關於緩沖區統計機制的內容。
3) 寫入文件:
在 NIO 中寫入文件類似於從文件中讀取。
// 首先從 FileOutputStream 獲取一個通道: FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" ); FileChannel fc = fout.getChannel(); // 下一步是創建一個緩沖區並在其中放入一些數據,這里,用message來表示一個持有數據的數組。 ByteBuffer buffer = ByteBuffer.allocate( 1024 ); for (int i=0; i<message.length; ++i) { buffer.put( message[i] ); } buffer.flip(); // 最后一步是寫入緩沖區中: fc.write( buffer );
注意在這里同樣不需要告訴通道要寫入多數據。緩沖區的內部統計機制會跟蹤它包含多少數據以及還有多少數據要寫入。
4) 讀寫結合:
最后將展示使用讀寫結合,將一個文件的所有內容拷貝到另一個文件中。
/** * 將一個文件的所有內容拷貝到另一個文件中。 * * CopyFile.java 執行三個基本操作: * 首先創建一個 Buffer,然后從源文件中將數據讀到這個緩沖區中,然后將緩沖區寫入目標文件。 * 程序不斷重復 — 讀、寫、讀、寫 — 直到源文件結束。 * */ public class CopyFile { public static void main(String[] args) throws Exception { String infile = "GitHub.txt"; String outfile = "GitHub2.txt"; // 獲取源文件和目標文件的輸入輸出流 FileInputStream fin = new FileInputStream(infile); FileOutputStream fout = new FileOutputStream(outfile); // 獲取輸入輸出通道 FileChannel fcin = fin.getChannel(); FileChannel fcout = fout.getChannel(); // 創建緩沖區 ByteBuffer buffer = ByteBuffer.allocate(1024); while (true) { // clear方法重設緩沖區,使它可以接受讀入的數據 buffer.clear(); // 從輸入通道中將數據讀到緩沖區 int r = fcin.read(buffer); // read方法返回讀取的字節數,可能為零,如果該通道已到達流的末尾,則返回-1 if (r == -1) { break; } // flip方法讓緩沖區可以將新讀入的數據寫入另一個通道 buffer.flip(); // 從輸出通道中將數據寫入緩沖區 fcout.write(buffer); } } }
下一篇我們將會深度講解一下NIO中的緩沖區Buffer。