1. 程式人生 > >Java之NIO(一)Channel和Buffer

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準備好寫資料了,但是不會覆蓋未讀的資料。