I/O即輸入輸出,是計算機與外界世界的一個藉口。IO操做的實際主題是操做系統。在java編程中,通常使用流的方式來處理IO,全部的IO都被視做是單個字節的移動,經過stream對象一次移動一個字節。流IO負責把對象轉換爲字節,而後再轉換爲對象。java
關於Java IO相關知識請參考個人另外一篇文章:Java IO 詳解編程
NIO即New IO,這個庫是在JDK1.4中才引入的。NIO和IO有相同的做用和目的,但實現方式不一樣,NIO主要用到的是塊,因此NIO的效率要比IO高不少。api
在Java API中提供了兩套NIO,一套是針對標準輸入輸出NIO,另外一套就是網絡編程NIO,本篇文章重點介紹標NIO,關於網絡編程NIO請見Java NIO詳解(二)。數組
NIO和IO最大的區別是數據打包和傳輸方式。IO是以流的方式處理數據,而NIO是以塊的方式處理數據。網絡
面向流的IO一次一個字節的處理數據,一個輸入流產生一個字節,一個輸出流就消費一個字節。爲流式數據建立過濾器就變得很是容易,連接幾個過濾器,以便對數據進行處理很是方便而簡單,可是面向流的IO一般處理的很慢。異步
面向塊的IO系統以塊的形式處理數據。每個操做都在一步中產生或消費一個數據塊。按塊要比按流快的多,但面向塊的IO缺乏了面向流IO所具備的有雅興和簡單性。ui
Buffer和Channel是標準NIO中的核心對象(網絡NIO中還有個Selector核心對象,具體請參考Java NIO詳解(二)),幾乎每個IO操做中都會用到它們。this
Channel是對原IO中流的模擬,任何來源和目的數據都必須經過一個Channel對象。一個Buffer實質上是一個容器對象,發給Channel的全部對象都必須先放到Buffer中;一樣的,從Channel中讀取的任何數據都要讀到Buffer中。操作系統
Buffer是一個對象,它包含一些要寫入或讀出的數據。在NIO中,數據是放入buffer對象的,而在IO中,數據是直接寫入或者讀到Stream對象的。應用程序不能直接對 Channel 進行讀寫操做,而必須經過 Buffer 來進行,即 Channel 是經過 Buffer 來讀寫數據的。.net
在NIO中,全部的數據都是用Buffer處理的,它是NIO讀寫數據的中轉池。Buffer實質上是一個數組,一般是一個字節數據,但也能夠是其餘類型的數組。但一個緩衝區不只僅是一個數組,重要的是它提供了對數據的結構化訪問,並且還能夠跟蹤系統的讀寫進程。
使用 Buffer 讀寫數據通常遵循如下四個步驟:
當向 Buffer 寫入數據時,Buffer 會記錄下寫了多少數據。一旦要讀取數據,須要經過 flip() 方法將 Buffer 從寫模式切換到讀模式。在讀模式下,能夠讀取以前寫入到 Buffer 的全部數據。
一旦讀完了全部的數據,就須要清空緩衝區,讓它能夠再次被寫入。有兩種方式能清空緩衝區:調用 clear() 或 compact() 方法。clear() 方法會清空整個緩衝區。compact() 方法只會清除已經讀過的數據。任何未讀的數據都被移到緩衝區的起始處,新寫入的數據將放到緩衝區未讀數據的後面。
Buffer主要有以下幾種:
Channel是一個對象,能夠經過它讀取和寫入數據。能夠把它看作IO中的流。可是它和流相比還有一些不一樣:
正如上面提到的,全部數據都經過Buffer對象處理,因此,您永遠不會將字節直接寫入到Channel中,相反,您是將數據寫入到Buffer中;一樣,您也不會從Channel中讀取字節,而是將數據從Channel讀入Buffer,再從Buffer獲取這個字節。
由於Channel是雙向的,因此Channel能夠比流更好地反映出底層操做系統的真實狀況。特別是在Unix模型中,底層操做系統一般都是雙向的。
在Java NIO中Channel主要有以下幾種類型:
IO中的讀和寫,對應的是數據和Stream,NIO中的讀和寫,則對應的就是通道和緩衝區。NIO中從通道中讀取:建立一個緩衝區,而後讓通道讀取數據到緩衝區。NIO寫入數據到通道:建立一個緩衝區,用數據填充它,而後讓通道用這些數據來執行寫入。
咱們已經知道,在NIO系統中,任什麼時候候執行一個讀操做,您都是從Channel中讀取,而您不是直接從Channel中讀取數據,由於全部的數據都必須用Buffer來封裝,因此您應該是從Channel讀取數據到Buffer。
所以,若是從文件讀取數據的話,須要以下三步:
下面咱們看一下具體過程:
第一步:獲取通道
FileInputStream fin = new FileInputStream( "readandshow.txt" ); FileChannel fc = fin.getChannel(); 123
第二步:建立緩衝區
ByteBuffer buffer = ByteBuffer.allocate( 1024 ); 12
第三步:將數據從通道讀到緩衝區
fc.read( buffer ); 12
相似於從文件讀數據,
第一步:獲取一個通道
FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" ); FileChannel fc = fout.getChannel(); 123
第二步:建立緩衝區,將數據放入緩衝區
ByteBuffer buffer = ByteBuffer.allocate( 1024 ); for (int i=0; i<message.length; ++i) { buffer.put( message[i] ); } buffer.flip(); 1234567
第三步:把緩衝區數據寫入通道中
fc.write( buffer ); 12
CopyFile是一個很是好的讀寫結合的例子,咱們將經過CopyFile這個實力讓你們體會NIO的操做過程。CopyFile執行三個基本的操做:建立一個Buffer,而後從源文件讀取數據到緩衝區,而後再將緩衝區寫入目標文件。
/** * 用java NIO api拷貝文件 * @param src * @param dst * @throws IOException */ public static void copyFileUseNIO(String src,String dst) throws IOException{ //聲明源文件和目標文件 FileInputStream fi=new FileInputStream(new File(src)); FileOutputStream fo=new FileOutputStream(new File(dst)); //得到傳輸通道channel FileChannel inChannel=fi.getChannel(); FileChannel outChannel=fo.getChannel(); //得到容器buffer ByteBuffer buffer=ByteBuffer.allocate(1024); while(true){ //判斷是否讀完文件 int eof =inChannel.read(buffer); if(eof==-1){ break; } //重設一下buffer的position=0,limit=position buffer.flip(); //開始寫 outChannel.write(buffer); //寫完要重置buffer,重設position=0,limit=capacity buffer.clear(); } inChannel.close(); outChannel.close(); fi.close(); fo.close(); } 12345678910111213141516171819202122232425262728293031323334
上面程序中有三個地方須要注意
當沒有更多的數據時,拷貝就算完成,此時 read() 方法會返回 -1 ,咱們能夠根據這個方法判斷是否讀完。
int r= fcin.read( buffer ); if (r==-1) { break; } 12345
flip、clear這兩個方法即是用來設置這些值的。
咱們先看一下flip的源碼:
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
在上面的FileCopy程序中,寫入數據以前咱們調用了buffer.flip();
方法,這個方法把當前的指針位置position設置成了limit,再將當前指針position指向數據的最開始端,咱們如今能夠將數據從緩衝區寫入通道了。 position 被設置爲 0,這意味着咱們獲得的下一個字節是第一個字節。 limit 已被設置爲原來的 position,這意味着它包括之前讀到的全部字節,而且一個字節也很少。
先看一下clear的源碼:
public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
在上面的FileCopy程序中,寫入數據以後也就是讀數據以前,咱們調用了 buffer.clear();
方法,這個方法重設緩衝區以便接收更多的字節。上圖顯示了在調用 clear() 後緩衝區的狀態。
轉載請說明出處,原文連接:http://blog.csdn.net/suifeng3051/article/details/48160753