天天看點

java NIO 的通道Channel的了解

一、什麼是通道?

Channel,中文意思“通道”,表示IO源與目标打開的連接配接,類似于傳統的“流”。但是Channel不能直接通路資料,

需要和緩沖區buffer進行互動。打個比喻:山西有煤,我們想要,于是乎建了一條鐵路連通到山西,

這條鐵路就是這裡的"Channel",那麼煤通過什麼運過來呢?鐵路建好了,就差火車了,

是以這裡的火車就像是緩沖區,火車把山西的煤運到這邊來,把我們這裡的錢運過去,

這樣雙向傳輸就愉快的完成了。Channel類似于傳統的“流”,隻不過Channel不能直接通路資料,

Channel隻能與buffer互動。而且流是單向的,分為輸入流和輸出流, 但是通道是雙向的,

通過緩沖區buffer兩邊都可達。Channel表示IO源與目标打開的連接配接。工作原理如下,通過Channel來獨立處理IO請求:

java NIO 的通道Channel的了解

二、主要實作類:

   FileChannel:用于讀取、寫入、映射和操作檔案的通道。

DatagramChannel:通過UDP讀寫網絡中的資料通道。

SocketChannel:通過tcp讀寫網絡中的資料。

ServerSocketChannel:可以監聽新進來的tcp連接配接,對每一個連接配接都建立一個SocketChannel。

三、擷取通道的方式

1、通過getChannel()方法擷取

前提是該類支援該方法。支援該類的方法有:

FileInputStream/FileOutputStream,RandomAccessFile,Socket,ServerSocket ,DatagramSocket

2、通過靜态方法open()

3、通過jdk1.7中Files的newByteChannel()方法

四、通道資料傳輸

通過一段複制圖檔的代碼解釋:

1、利用通道和非直接緩沖區完成

FileInputStream fis=null;           //引用
		FileOutputStream fout=null;
		FileChannel channel=null;		//通道引用
		FileChannel outchannel=null;
		try {
			fis = new FileInputStream("sb.jpg");     //源檔案
			fout = new FileOutputStream("bb.jpg");	 //目标檔案	  	
			channel = fis.getChannel();             //擷取連接配接源檔案的通道
			outchannel = fout.getChannel();	        //擷取連接配接目标檔案的通道
			//指定緩沖區 非直接緩沖區
			ByteBuffer buffer=ByteBuffer.allocate(1024);  //建立緩沖區 用來傳輸資料
			while(channel.read(buffer)!=-1)          //從連接配接源檔案的管道讀取資料到緩沖區
			{
				//将緩沖區反轉
				buffer.flip();
				outchannel.write(buffer);        //将緩沖區中的資料寫入連接配接到目标檔案的管道
				buffer.clear();                  //"清空"緩沖區
			}
           

2、利用通道和記憶體映射檔案完成

FileChannel inChannel=FileChannel.open(Paths.get("C:\\Users\\Administrator\\Pictures\\picture\\1.jpg"), StandardOpenOption.READ);            
//參數1表示目标檔案或者源檔案的路徑,參數2是可以是多個參數,表示通道支援的操作類型
           
FileChannel outChannel=FileChannel.open(Paths.get("C:\\Users\\Administrator\\Pictures\\picture\\1bar.jpg"),
StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
		//記憶體映射檔案 相當于之前的緩沖區
		MappedByteBuffer inBuffer=inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());		
		MappedByteBuffer outBuffer=outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
		//直接對緩沖區讀寫操作
		byte[] data=new byte[inBuffer.limit()];
		//将資料讀入位元組數組
		inBuffer.get(data);
		outBuffer.put(data);   //寫入管道
		inChannel.close();
		outChannel.close();
           
對于上面這種方式,可以很大程度上提高程式的效率。那麼怎麼提高的呢? 回到最上面的那張通道工作的原理圖,使用者位址和記憶體位址都有一個緩沖區,它們之間有一個copy過程,也就是說程式的資料要先到jvm使用者位址緩沖區,再到計算機記憶體中。也就是不能直接和計算機記憶體進行資料操作。那麼通過記憶體映射檔案呢,也就是上訴方式,内部以直接緩沖區和記憶體映射檔案實作了直接通路記憶體。這樣大大提高了效率。但是也有缺點,如果寫過去的資料很大,然後jvm沒有及時回收記憶體,那麼結果可想而知。是以,這個适用于那種長久存放在記憶體不應該被短時間内就需要回收的資料。 3、通道之間的直接傳輸
FileChannel inChannel=FileChannel.open(Paths.get("C:\\Users\\Administrator\\Pictures\\picture\\1.jpg"), StandardOpenOption.READ);
		FileChannel outChannel=FileChannel.open(Paths.get("C:\\Users\\Administrator\\Pictures\\picture\\1bar.jpg"), 
           
StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
		//transfer方法   内部是直接緩沖區的方式實作
		//inChannel.transferTo(0,inChannel.size(), outChannel);
		outChannel.transferFrom(inChannel, 0, inChannel.size());
		inChannel.close();
		outChannel.close();
           
咦?為什麼上面的代碼沒有緩沖區呢?不是說通道不能直接通路資料嗎? 沒錯,隻不過在transferTo和transferFrom内部實作了緩沖區,是以資料還是通過緩沖區來完成的。 關于transferTo和transferFrom,兩者達到的效果一樣,隻是使用的方式不同,前者是源檔案到目标檔案, 後者是目标檔案的資源來自于源檔案。 4、分散于聚集 分散讀取:是指讀取一個Channel的資料到多個buffer中,注意是依次填滿每個緩沖區,直到沒有資料了; 聚集寫入:是指将多個buffer的内容依次寫入到通道中。
RandomAccessFile randomAccessFile=new RandomAccessFile("1.txt", "rw");
		FileChannel channel=randomAccessFile.getChannel();//擷取通道
		//配置設定指定大小緩沖區  這裡指定兩個大小不一樣的緩沖區
		ByteBuffer buf1=ByteBuffer.allocate(100);
		ByteBuffer buf2=ByteBuffer.allocate(4096);
		ByteBuffer[] buf={buf1,buf2};
		channel.read(buf);
		for (ByteBuffer byteBuffer : buf) {
			byteBuffer.flip();     //翻轉 開啟讀模式
		}
		//列印buf1的内容
		System.out.println(new String(buf[0].array(),0,buf[0].limit()));
		//列印buf2的内容
		System.out.println(new String(buf[1].array(),0,buf[1].limit()));
		//聚集寫入
		RandomAccessFile randFile=new RandomAccessFile("2.txt", "rw");
		FileChannel fileChannel=randFile.getChannel();
		fileChannel.write(buf);