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的概述可見譯文:

http://ifeve.com/channels/

/**
 * 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準備好寫數據了,但是不會覆蓋未讀的數據。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章