Java - NIO之Channel(FileChannel)

襲冷發表於2014-05-25

一、關於Channel

    Java NIO的通道(Channel)類似流,但又有些不同:

        既可以從通道中讀取資料,又可以寫資料到通道。但流的讀寫通常是單向的。

        通道可以非同步地讀寫。

        通道中的資料總是要先讀到一個Buffer,或者總是要從一個Buffer中寫入。

 

    Java NIO中最重要的通道的實現:

        FileChannel 從檔案中讀寫資料。

        DatagramChannel 能通過UDP讀寫網路中的資料。

        SocketChannel 能通過TCP讀寫網路中的資料。

        ServerSocketChannel可以監聽新進來的TCP連線,像Web伺服器那樣。對每一個新進來的連線都會建立一個SocketChannel。


二、關於FileChannel

    Java NIO中的FileChannel是一個連線到檔案的通道,可以通過檔案通道讀寫檔案。

    FileChannel無法設定為非阻塞模式,它總是執行在阻塞模式下。


三、讀取檔案

		/**
		 * 獲取檔案控制程式碼(相對於專案根路徑)
		 */
		RandomAccessFile file = new RandomAccessFile("nio-data.txt", "rw");

		/**
		 * 獲取Channel
		 */
		FileChannel channel = file.getChannel();

		/**
		 * 定義Buffer緩衝區,存放讀取到的資料。          大小 48byte
		 */
		ByteBuffer buf = ByteBuffer.allocate(48); 

		/**
		 * 從Channel中讀取資料寫到Buffer
		 */
		int bytesRead = channel.read(buf);

		/**
		 * 迴圈讀取(判斷是否讀取完畢)
		 */
		while (bytesRead != -1) {	
						
			/**
			 * 翻轉Buffer的讀寫模式
			 */
			buf.flip();
			
			/**
			 * 列印當前讀取到的資料
			 */
			while(buf.hasRemaining()){
				System.out.print((char) buf.get());
			}

			
			/**
			 * 清空緩衝區,讓它可以再次被寫入
			 */
			buf.clear();  
			
			/**
			 * 繼續從Channel中讀取資料到Buffer
			 */
			bytesRead = channel.read(buf);  
		}
		
		/**
		 * 關閉資源
		 */
		channel.close();
		file.close();
四、寫入檔案

		/**
		 * 準備工作
		 */
		String newData = "New String to write to file..." + System.currentTimeMillis();
		
		RandomAccessFile file = new RandomAccessFile("nio-data.txt", "rw");
		FileChannel channel = file.getChannel();
		
		ByteBuffer buf = ByteBuffer.allocate(48);
		buf.clear();
		
		buf.put(newData.getBytes());

		buf.flip();

		/**
		 * 因為無法保證write()方法一次能向FileChannel寫入多少位元組,因此需要重複呼叫write()方法,直到Buffer中已經沒有尚未寫入通道的位元組
		 */
		while(buf.hasRemaining()) {
			channel.write(buf);
		}
		
		channel.close();
		file.close();

    注意:因為沒有指定寫入的開始位置,如果原始檔中已經存在資料,這裡的寫入會從原始檔的開始位置替換到寫入資料的結束。


五、特定位置讀寫

    對於某個特定位置進行資料的讀/寫操作,可以通過呼叫position()方法獲取FileChannel的當前位置, 也可以通過呼叫position(long pos)方法設定FileChannel的當前位置。

    size()方法將返回該例項所關聯檔案的大小

    force(boolean metaData)方法將通道里尚未寫入磁碟的資料強制寫到磁碟上,metaData為true將包含許可權

		/**
		 * 準備資料
		 */
		String newData = "New String to write to file..." + System.currentTimeMillis();
		
		RandomAccessFile file = new RandomAccessFile("nio-data.txt", "rw");
		FileChannel channel = file.getChannel();
		
		/**
		 * 指定position為檔案大小的值,即在channel的末尾追加內容 
		 */
		channel.position(channel.size());

		ByteBuffer buf = ByteBuffer.allocate(48);
		buf.clear();
		
		buf.put(newData.getBytes());
		
		buf.flip();

		while(buf.hasRemaining()) {
			channel.write(buf);
		}
		
		channel.close();
		file.close();
六、檔案擷取

    對檔案本身發生作用,需要寫許可權

		RandomAccessFile file = new RandomAccessFile("nio-data.txt", "rw");
		FileChannel channel = file.getChannel();
		
		/**
		 * 擷取檔案前40byte
		 */
		channel.truncate(40);
		
		channel.close();
		file.close();
七、Channel-to-Channel傳輸

    FileChannel類有這兩個獨有的方法方法:transferFrom()和testTransferTo(),因此Channel-to-Channel傳輸中通道之一必須是FileChannel。

    直接的通道傳輸不會更新與某個FileChannel關聯的position值。

    對於傳輸資料來源是一個檔案的transferTo()方法,如果position + count的值大於檔案的size值,傳輸會在檔案尾的位置終止。

	/**
	 * 測試TransferFrom()
	 */
	@org.junit.Test
	public void testTransferFrom() throws Exception{
		/**
		 * 來源相關
		 */
		RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
		FileChannel fromChannel = fromFile.getChannel();

		/**
		 * 目標相關
		 */
		RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
		FileChannel toChannel = toFile.getChannel();

		/**
		 * 傳輸量
		 */
		long position = 0;
		long count = fromChannel.size();

		/**
		 * 執行傳輸(FileChannel to FileChannel)
		 */
		toChannel.transferFrom(fromChannel, position, count);
		
		toChannel.close();
		toFile.close();
		fromChannel.close();
		fromFile.close();
	}
	/**
	 * 測試TransferTo()
	 */
	@org.junit.Test
	public void testTransferTo() throws Exception{
		/**
		 * 來源相關
		 */
		RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
		FileChannel fromChannel = fromFile.getChannel();
		
		/**
		 * 目標相關
		 */
		WritableByteChannel toChannel = Channels.newChannel(System.out);
		
		/**
		 * 執行傳輸(檔案通道 to 普通通道)
		 */
		fromChannel.transferTo(0, fromChannel.size(), toChannel);
		fromChannel.close();
		fromFile.close();
	}
 
 
 
 

相關文章