Java NIO學習系列六:Java中的IO模型
前文中我們總結了linux系統中的5中IO模型,並且著重介紹了其中的4種IO模型:
- 阻塞I/O(blocking IO)
- 非阻塞I/O(nonblocking IO)
- I/O多路複用(IO multiplexing)
- 非同步I/O(asynchronous IO)
但是前面總結的IO模型只是限定在linux下,更偏向於作業系統底層的概念,並沒有涉及到Java應用層面,其實Java中也提供了和前面作業系統層面的IO模型相對應的概念,這是本文接下來要講的重點。
同樣本文會圍繞如下幾點進行展開:
I/O模型在Java中的對應
適用場景
Java中各種IO模型的使用方式
總結
1. I/O模型在Java中的對應
1.1 阻塞I/O
傳統Java IO提供的面向流的IO操作方式就屬於阻塞式的,呼叫其read()或write()方法的執行緒會阻塞,直到完成了資料的讀寫,在讀寫的過程中執行緒是什麼都做不了的。
1.2 非阻塞I/O
Java NIO類庫提供了多種支援非阻塞模式的類,比如Channel、Buffer,可以將其設定為非阻塞模式,執行緒向channel請求讀資料時,只會獲取已經就緒的資料,並不會阻塞以等待所有資料都準備好,這樣在資料準備的階段執行緒就能夠去處理別的事情,這就是非阻塞式讀,對於寫資料是一樣的。
這裡和上面阻塞的區別就是,呼叫read()或write()方法並不阻塞,而是會立即返回,但是這時候IO操作往往是還沒有結束的。
1.3 多路複用
Java NIO中的Selector允許單個執行緒監控多個channel,可以將多個channel註冊到一個Selector中,然後可以"select"出已經準備好資料的channel,或者準備好寫入的channel,然後對其進行讀或者寫資料,這就是多路複用。
1.4 非同步IO
非同步IO模型是比較理想的IO模型,在非同步IO模型中,當用戶執行緒發起read操作之後,立刻就可以開始去做其它的事。另一方面,核心會等待資料準備完成,然後將資料複製到使用者執行緒,當這一切都完成之後,核心會給使用者執行緒傳送一個訊號,告訴它read操作完成了。也就是說使用者執行緒完全不需要關心實際的整個IO操作了,只需要發起請求就行了,當收到核心的成功訊號時就可以直接去使用資料了。這就是和非阻塞式的區別,如果說阻塞式IO是完全手動,非阻塞式IO就是半自動,而非同步IO就是全自動,多路複用呢?我覺得可以是半自動衝鋒槍^_^
在Java 7中,提供了Asynchronous IO,Java NIO中的AsynchronousFileChannel支援非同步模型實現的。
2. 適用場景
BIO方式適用於連線數目比較小且每個連線佔用大量寬頻,這種方式對伺服器資源要求比較高,JDK1.4以前的唯一選擇,但程式直觀簡單易理解。
NIO方式適用於連線數目多且連線比較短(輕操作)的架構,比如聊天伺服器,程式設計比較複雜,JDK1.4開始支援。
AIO方式適用於連線數目多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫OS參與併發操作,程式設計比較複雜,JDK7開始支援。
3. Java中各種IO模型的使用方式
前面講了這麼多,即講了linux下的IO模型,又講了Java中對這些IO模型的支援,到這裡我覺得是時候找一些Java中實際的例子看看,下面就分別用三種IO模型來讀寫檔案。
3.1 通過BIO方式讀寫檔案
public void rwByBIO() { BufferedReader br = null; FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream("test.txt"); out = new FileOutputStream("testBIO.txt"); List<Integer> list = new ArrayList(); int temp; while((temp = in.read()) != -1) { out.write(temp); } br = new BufferedReader(new InputStreamReader(new FileInputStream("testBIO.txt"))); System.out.println(br.readLine()); }catch(Exception e) { e.printStackTrace(); }finally { if(br != null) { try { br.close(); }catch(IOException e) { e.printStackTrace(); } } if(out != null) { try { out.close(); }catch(IOException e) { e.printStackTrace(); } } } }
在根目錄下準備好檔案test.txt,裡面寫上準備好的內容,比如"黃沙百戰穿金甲,不破樓蘭終不還",然後跑起來,之後應該會多出一個檔案testBIO.txt,裡面內容是一樣的。我們通過BIO的方式讀取test.txt中的內容,同樣以BIO的方式寫入到testBIO.txt中。
3.2 通過NIO讀寫檔案
public void rwByNIO() { FileChannel readChannel = null; FileChannel writeChannel = null; try { readChannel = new RandomAccessFile(new File("test.txt"),"r").getChannel(); writeChannel = new RandomAccessFile(new File("testNIO.txt"),"rw").getChannel(); ByteBuffer buffer = ByteBuffer.allocate(10); int bytesRead = readChannel.read(buffer); while(bytesRead != -1) { buffer.flip(); while(buffer.hasRemaining()) { // 寫入檔案 writeChannel.write(buffer); } // 一次寫完之後 buffer.clear(); bytesRead = readChannel.read(buffer); } }catch(Exception e) { e.printStackTrace(); }finally { if(readChannel != null) { try { readChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(writeChannel != null) { try { writeChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
這裡是通過NIO中的FileChannel來讀寫檔案,但是要注意,雖然這一節的標題是說用NIO的方式來讀寫檔案,但是FileChannel並不支援非阻塞模式,所以其實際上還是屬於阻塞的,即BIO的方式,只是因為這裡為了統一演示讀寫檔案的例子,所以仍然使用NIO中的FileChannel類來完成。
3.3 通過AIO方式讀寫檔案
public void rwByAIO() { Path path = Paths.get("test.txt"); AsynchronousFileChannel fileChannel = null; try { fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ); ByteBuffer buffer = ByteBuffer.allocate(1024); long position = 0; Future<Integer> operation = fileChannel.read(buffer, position); while(!operation.isDone()); buffer.flip(); Path writePath = Paths.get("testAIO.txt"); if(!Files.exists(writePath)){ Files.createFile(writePath); } AsynchronousFileChannel writeFileChannel = AsynchronousFileChannel.open(writePath, StandardOpenOption.WRITE); writeFileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer attachment) { System.out.println("bytes written: " + result); } @Override public void failed(Throwable exc, ByteBuffer attachment) { System.out.println("Write failed"); exc.printStackTrace(); } }); }catch(Exception e) { e.printStackTrace(); } }
這個例子中是通過非同步地方式來讀寫檔案。當呼叫了Java NIO中的AsynchronousFileChannel對這種操作提供了支援,當呼叫其read()方法時會立即返回一個Future物件,通過呼叫其isDone方法來得知資料是否讀取完畢。
4. 總結
本文結合前文講到的IO模型,分別對應到Java中的具體類庫實現,並通過例子演示了BIO、NIO、AIO三種方式讀寫檔案。
- 標準Java IO提供的面向流的方式屬於BIO模型的實現,在讀取的過程中是會阻塞的;
- Java NIO提供的Channel和Buffer是支援NIO模式的,呼叫了Channel的讀寫方法之後可以立即返回,在往Buffer中準備資料的過程中是不阻塞的,執行緒可以做別的事情,但是從Buffer讀寫資料是阻塞的;
- Java NIO中提供的AsynchronousFileChannel支援非同步讀寫檔案,當呼叫了其讀寫方法之後可以立即返回,只需要等待系統把資料複製到指定位置即可,整個過程都不會阻塞;
參考文獻
bio-vs-nio-vs-aio
linu