初識 Java NIO
一、初識nio
在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 類, 引入了一種基於通道和緩沖區的 I/O 方式,它可以使用 Native 函數庫直接分配堆外內存,然後通過一個存儲在 Java 堆的 DirectByteBuffer 對象作為這塊內存的引用進行操作,避免了在 Java 堆和 Native 堆中來回復制數據。
NIO 是一種同步非阻塞的 IO 模型。同步是指線程不斷輪詢 IO 事件是否就緒,非阻塞是指線程在等待 IO 的時候,可以同時做其他任務。同步的核心就是 Selector,Selector 代替了線程本身輪詢 IO 事件,避免了阻塞同時減少了不必要的線程消耗;非阻塞的核心就是通道和緩沖區,當 IO 事件就緒時,可以通過寫道緩沖區,保證 IO 的成功,而無需線程阻塞式地等待。
二、NIO中的重要概念
1、緩沖區(buffer)
NIO是基於緩沖區的IO方式。當一個鏈接建立完成後,IO的數據未必會馬上到達,為了使數據到達時能夠正確完成IO操作,在BIO(阻塞IO)中,等待IO的線程必須被阻塞,以全天候地執行IO操作。為了解決這種IO方式低效的問題,引入了緩沖區的概念,當數據到達時,可以預先被寫入緩沖區,再由緩沖區交給線程,因此線程無需阻塞地等待。
常用緩沖區類型:
ByteBuffer、MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer
緩沖區常用方法:
allocate() - 分配一塊緩沖區
put() - 向緩沖區寫數據
get() - 向緩沖區讀數據
filp() - 將緩沖區從寫模式切換到讀模式
clear() - 從讀模式切換到寫模式,不會清空數據,但後續寫數據會覆蓋原來的數據,即使有部分數據沒有讀,也會被遺忘;
compact() - 從讀數據切換到寫模式,數據不會被清空,會將所有未讀的數據copy到緩沖區頭部,後續寫數據不會覆蓋,而是在這些數據之後寫數據
mark() - 對position做出標記,配合reset使用
reset() - 將position置為標記值
緩沖區的一些屬性:
capacity - 緩沖區大小,無論是讀模式還是寫模式,此屬性值不會變;
position - 寫數據時,position表示當前寫的位置,每寫一個數據,會向下移動一個數據單元,初始為0;最大為capacity - 1;切換到讀模式時,position會被置為0,表示當前讀的位置
limit - 寫模式下,limit 相當於capacity 表示最多可以寫多少數據,切換到讀模式時,limit 等於原先的position,表示最多可以讀多少數據。
2、通道
通道是 I/O 傳輸發生時通過的入口,而緩沖區是這些數據傳輸的來源或目標。對於離開緩沖區的傳輸,您想傳遞出去的數據被置於一個緩沖區,被傳送到通道。對於傳回緩沖區的傳輸,一個通道將數據放置在您所提供的緩沖區中。
例如:有一個服務器通道 ServerSocketChannel serverChannel,一個客戶端通道 SocketChannel clientChannel;服務器緩沖區:serverBuffer,客戶端緩沖區:clientBuffer。當服務器想向客戶端發送數據時,需要調用:clientChannel.write(serverBuffer)。當客戶端要讀時,調用 clientChannel.read(clientBuffer);當客戶端想向服務器發送數據時,需要調用:serverChannel.write(clientBuffer)。當服務器要讀時,調用 serverChannel.read(serverBuffer)。
常用通道類型
FileChannel:從文件中讀寫數據。
DatagramChannel:能通過UDP讀寫網絡中的數據。
SocketChannel:能通過TCP讀寫網絡中的數據。
ServerSocketChannel:可以監聽新進來的TCP連接,像Web服務器那樣。對每一個新進來的連接都會創建一個SocketChannel。
3、選擇器(selector)
通道和緩沖區的機制,使得線程無需阻塞地等待IO事件的就緒,但是總是要有人來監管這些IO事件。這個工作就交給了selector來完成,這就是所謂的同步。要使用Selector,得向Selector註冊Channel,然後調用它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒,這就是所說的輪詢。一旦這個方法返回,線程就可以處理這些事件。
通道向選擇器註冊時,需要指定感興趣的事件,選擇器支持以下事件:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
三、小實例
使用nio簡單實現了文件的復制。
public class Test { public static void main(String[] args) { FileInputStream fin = null; FileOutputStream fout = null; FileChannel fic = null; FileChannel foc = null; try { fin = new FileInputStream("F:\\1.txt"); fout = new FileOutputStream("F:\\2.txt"); //從FileInputStream創建用於輸入的FileChannel fic = fin.getChannel(); //從FileOutputStream創建用於輸出的FileChannel foc = fout.getChannel(); //建立buffer緩沖區,2的8次方 ByteBuffer buf = ByteBuffer.allocate(1024<<8); //根據read返回實際獨處的字節數,終止循環 //緩沖區從fic讀取數據 while(fic.read(buf)>0) { // 緩沖區翻轉用於輸出數據到focus buf.flip(); foc.write(buf); //清空緩沖區用於下次讀取 buf.clear(); } //安全釋放資源 if(fic != null) fic.close(); if(foc != null) foc.close(); if(fin != null) fin.close(); if(fout != null) fout.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { } } }
PS:因本人能力有限,如有誤還請諒解;
初識 Java NIO