Java之NIO(一)Channel和Buffer
java NIO 就是NEW I O,他與傳統IO的最大的區別是 它是非阻塞IO。
Java NIO和IO之間的主要差別:
IO NIO 面向流 面向緩衝 阻塞IO 非阻塞IO 無 選擇器 |
他們各自適用於不同的環境,這裡只簡單的說明其區別,具體的見部落格:
http://ifeve.com/java-nio-vs-io/
NIO的核心api 是 Channel、Buffer和Selector,本品文章著重介紹前兩種。
一個簡單例子
首先舉一個簡單的例子進行說明,這個例子使用NIO的api向一個檔案中寫入資料,然後把他讀出來列印,此例子的註釋很詳細,可細細品味,另外有關NIO的概述可見譯文:
/** * NIO 簡單測試例子,向一個檔案中寫入資料,然後把它讀出來。這個例子 * 主要說明 NIO 和傳統IO的不同,需要注意channel 和 buffer的使用。 * 理解 NIO 是面向緩衝的。而IO是面向流的。 * * 以下例子要理解NIO的使用 過程,1 定義一個 channel , 2,定義一個buffer * 3 向buffer寫資料 (寫的含義有兩種,與channel有關) 4 轉換為讀模式 5.讀取buffer資料 (讀的含義與channel的方向有關) * */ public class Main { public static void main(String[] args) throws IOException { File a = new File("test.txt"); if (!a.exists()) { a.createNewFile(); } FileOutputStream out = new FileOutputStream(a); //構造輸出channel,這裡使用filechannel FileChannel outChannel = out.getChannel(); String testStr = "Lina , i love you"; //構造一個輸出的快取。 ByteBuffer byteBuffer1 = ByteBuffer.allocate(512); //put方法向快取中寫資料,注意是寫 byteBuffer1.put(testStr.getBytes()); //下面的三行程式碼將向檔案中寫資料,filp()方法把buffer轉換為讀模式,然後使用channel的寫方法寫資料到檔案。 //注意的是,outChannel 寫入檔案的資料是從charbuffer“讀取”的, 寫入或者讀取 是針對buffer而言,不是根據channel說的,這點需要理解 byteBuffer1.flip(); while (byteBuffer1.hasRemaining()) { outChannel.write(byteBuffer1); } outChannel.close(); out.close(); FileInputStream in = new FileInputStream(a); //構造輸入channel和 buffer,跟以上對比,channel的方向是 底層的包裝層決定的。 FileChannel inChannel = in.getChannel(); ByteBuffer inBuffer = ByteBuffer.allocate(512); //迴圈讀取檔案資料,寫入到 buffer中,注意這裡還是寫入buffer while (inChannel.read(inBuffer)!=-1) { } //轉換為讀模式,flip方法呼叫後,buffer會發生一些變化,這裡暫不討論。 inBuffer.flip(); byte[] dest = new byte[inBuffer.limit()]; //讀取資料到byte陣列。 inBuffer.get(dest, 0, inBuffer.limit()); inChannel.close(); in.close(); System.out.println(new String(dest)); }
Channel
官方文件定義channel指“與一個實體的連線”,這個實體可以是一個裝置、檔案、socket等。channel的介面定義本身簡單,JDK提供了幾種實現。
public interface Channel extends Closeable {
public boolean isOpen();
public void close() throws IOException;
}
上圖展示了channel的體系,可見WritableByteChannel和ReadableByteChannel 分別擴充套件了channel介面,加入了讀和寫的功能,其中最常見的實現已經分別用紅色框起來,他們分別適用於不同的場合。
FileChannel 從檔案中讀寫資料,與fileinput/outputStream對應
DatagramChannel 能通過UDP讀寫網路中的資料。對應DatagramSocket
SocketChannel 能通過TCP讀寫網路中的資料。對應IO中的socket
ServerSocketChannel可以監聽新進來的TCP連線,像Web伺服器那樣。對每一個新進來的連線都會建立一個SocketChannel。對應IO中ServerSocket
Buffer
首先貼一張圖來表示Channel和Buffer的關係,即channel是通過buffer來讀寫資料的。關於buffer,http://ifeve.com/buffers/ ,以上地址翻譯的很好,這裡摘出裡面的部分內容。
基本用法
簡單的例子中,已經說明了channel和buffer的基本用法這裡,做個簡單的總結,
使用Buffer讀寫資料一般遵循以下四個步驟:
1. 寫入資料到Buffer
2. 呼叫flip()方法
3. 從Buffer中讀取資料
4. 呼叫clear()方法或者compact()方法
當向buffer寫入資料時,buffer會記錄下寫了多少資料。一旦要讀取資料,需要通過flip()方法將Buffer從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到buffer的所有資料。
一旦讀完了所有的資料,就需要清空緩衝區,讓它可以再次被寫入。有兩種方式能清空緩衝區:呼叫clear()或compact()方法。clear()方法會清空整個緩衝區。compact()方法只會清除已經讀過的資料。任何未讀的資料都被移到緩衝區的起始處,新寫入的資料將放到緩衝區未讀資料的後面。
Buffer的屬性
buffer有三個基本的屬性capacity limit position.
capacity 是指buffer的大小,在buffer建立的時候已經確定。
limit 當buffer處於寫模式,指還可以寫入多少資料,處於讀模式,指還有多少資料可以讀。
position 當buffer處於寫模式,指下一個寫資料的位置, 處於讀模式,當前將要讀取的資料的位置。每讀寫一個數據,position+1
也就是 limit 和position在 buffer的讀/寫時的含義不一樣。當呼叫buffer的flip方法,由寫模式變為讀模式時,
limit(讀)=position(寫)
position(讀) =0;
Buffer的型別
buffer有多種型別,不同的buffer提供不同的方式操作buffer中的資料。
buffer的操作
寫資料
寫資料到buffer有兩種情況:
1. 從channel寫到 buffer,如例子中channel從檔案中讀取資料,寫到channel
2. 直接呼叫put方法,往裡面寫資料
flip()
這個操作已經解釋過,轉換buffer為讀模式
讀資料
從Buffer中讀取資料有兩種方式:
1. 從Buffer讀取資料到Channel。
2. 使用get()方法從Buffer中讀取資料。
rewind()
Buffer.rewind()將position設回0,所以你可以重讀Buffer中的所有資料。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)。
clear() 和 compact()方法
一旦讀完Buffer中的資料,需要讓Buffer準備好再次被寫入。可以通過clear()或compact()方法來完成。
如果呼叫的是clear()方法,position將被設回0,limit被設定成 capacity的值。換句話說,Buffer 被清空了。Buffer中的資料並未清除,只是這些標記告訴我們可以從哪裡開始往Buffer裡寫資料。
如果Buffer中有一些未讀的資料,呼叫clear()方法,資料將“被遺忘”,意味著不再有任何標記會告訴你哪些資料被讀過,哪些還沒有。
如果Buffer中仍有未讀的資料,且後續還需要這些資料,但是此時想要先先寫些資料,那麼使用compact()方法。
compact()方法將所有未讀的資料拷貝到Buffer起始處。然後將position設到最後一個未讀元素正後面。limit屬性依然像clear()方法一樣,設定成capacity。現在Buffer準備好寫資料了,但是不會覆蓋未讀的資料。