1. 程式人生 > >JAVA NIO(三)通道Channel & 直接與非直接緩衝區

JAVA NIO(三)通道Channel & 直接與非直接緩衝區

1. 通道

負責目標節點與源節點的連線;傳輸資料。

在 Java NIO 中負責緩衝區中資料的傳輸。Channel 本身不儲存資料,因此需要配合緩衝區進行傳輸。

2.通道的主要實現類

  java.nio.channels.Channel 介面:
  |--FileChannel
  |--SocketChannel
  |--ServerSocketChannel   |--DatagramChannel

 3.獲取通道

① Java 針對支援通道的類提供了 getChannel() 方法
 本地 IO:
 FileInputStream/FileOutputStream
 RandomAccessFile
 網路IO:
 Socket
  ServerSocket
  DatagramSocket② 在 JDK 1.7 中的 NIO.2 針對各個通道提供了靜態方法 open()。
③ 在 JDK 1.7 中的 NIO.2 的 Files 工具類的 newByteChannel()。

4. 直接緩衝區與非直接緩衝區


位元組緩衝區要麼是直接的,要麼是非直接的。如果為直接位元組緩衝區,則 Java 虛擬機器會盡最大努力直接在此緩衝區上執行本機 I/O 操作。也就是說,在每次呼叫基礎作業系統的一個本機 I/O 操作之前(或之後),虛擬機器都會盡量避免將緩衝區的內容複製到中間緩衝區中(或從中間緩衝區中複製內容)。
直接位元組緩衝區可以通過呼叫此類的 allocateDirect() 工廠方法來建立此方法返回的緩衝區進行分配和取消分配所需成本通常高於非直接緩衝區。直接緩衝區的內容可以駐留在常規的垃圾回收堆之外,因此,它們對應用程式的記憶體需求量造成的影響可能並不明顯。所以,建議將直接緩衝區主要分配給那些易受基礎系統的本機 I/O 操作影響的大型、持久的緩衝區。一般情況下,最好僅在直接緩衝區能在程式效能方面帶來明顯好處時分配它們。直接位元組緩衝區還可以通過 FileChannel 的 map() 方法 將檔案區域直接對映到記憶體中來建立。該方法返回MappedByteBuffer
。Java 平臺的實現有助於通過 JNI 從本機程式碼建立直接位元組緩衝區。如果以上這些緩衝區中的某個緩衝區例項指的是不可訪問的記憶體區域,則試圖訪問該區域不會更改該緩衝區的內容,並且將會在訪問期間或稍後的某個時間導致丟擲不確定的異常。
@Test    //利用通道完成檔案的複製(非直接緩衝區)
public void test1(){//10874-10953
	long start = System.currentTimeMillis();
	
	FileInputStream fis = null;
	FileOutputStream fos = null;
	//①獲取通道
	FileChannel inChannel = null;
	FileChannel outChannel = null;
	try {
		fis = new FileInputStream("d:/1.mkv");
		fos = new FileOutputStream("d:/2.mkv");
		
		inChannel = fis.getChannel();
		outChannel = fos.getChannel();
		
		//②分配指定大小的緩衝區
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		//③將通道中的資料存入緩衝區中
		while(inChannel.read(buf) != -1){
			buf.flip(); //切換讀取資料的模式
			//④將緩衝區中的資料寫入通道中
			outChannel.write(buf);
			buf.clear(); //清空緩衝區
		}
	} catch (IOException e) {
		e.printStackTrace();
	} finally {
		if(outChannel != null){
			try {
				outChannel.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		if(inChannel != null){
			try {
				inChannel.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		if(fos != null){
			try {
				fos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		if(fis != null){
			try {
				fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	long end = System.currentTimeMillis();
	System.out.println("耗費時間為:" + (end - start));
	
}
//使用直接緩衝區完成檔案的複製(記憶體對映檔案)
@Test
public void test2() throws IOException{//2127-1902-1777
	long start = System.currentTimeMillis();
	
	FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ);
	FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), 
			StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
	
	//記憶體對映檔案
	MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
	MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
	
	//直接對緩衝區進行資料的讀寫操作
	byte[] dst = new byte[inMappedBuf.limit()];
	inMappedBuf.get(dst);
	outMappedBuf.put(dst);
	
	inChannel.close();
	outChannel.close();
	
	long end = System.currentTimeMillis();
	System.out.println("耗費時間為:" + (end - start));
}

5.通道之間的資料傳輸(直接緩衝區)

//通道之間的資料傳輸(直接緩衝區)
@Test
public void test3() throws IOException{
	FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ);
	FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), 
			StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
	
//	inChannel.transferTo(0, inChannel.size(), outChannel);
	outChannel.transferFrom(inChannel, 0, inChannel.size());
	
	inChannel.close();
	outChannel.close();
}

6.分散(Scatter)與聚集(Gather)

分散讀取(Scattering Reads):將通道中的資料分散到多個緩衝區中
聚集寫入(Gathering Writes):將多個緩衝區中的資料聚集到通道中
//分散和聚集
@Test
public void test4() throws IOException{
	RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");
	
	//1. 獲取通道
	FileChannel channel1 = raf1.getChannel();
	
	//2. 分配指定大小的緩衝區
	ByteBuffer buf1 = ByteBuffer.allocate(100);
	ByteBuffer buf2 = ByteBuffer.allocate(1024);
	
	//3. 分散讀取
	ByteBuffer[] bufs = {buf1, buf2};
	channel1.read(bufs);
	
	for (ByteBuffer byteBuffer : bufs) {
		byteBuffer.flip();
	}
	
	System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
	System.out.println("-----------------");
	System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));
	
	//4. 聚集寫入
	RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
	FileChannel channel2 = raf2.getChannel();
	
	channel2.write(bufs);
}