基本概念
io是主存和外部裝置(硬碟、終端和網絡等)拷貝資料的過程。io是作業系統的底層功能實作,底層通過i/o指令進行完成。
所有語言運作時系統提供執行i/o較進階别的工具。(c的printf scanf,java的面向對象封裝)
java 标準io回顧
java标準io類庫是io面向對象的一種抽象。基于本地方法的底層實作,我們無須關注底層實作。 inputstreamoutputstream(位元組流):一次傳送一個位元組。 readerwriter(字元流):一次一個字元。
nio簡介
nio是java new io的簡稱,在jdk1.4裡提供的新api。sun官方标榜的特性如下:
– 為所有的原始類型提供(buffer)緩存支援。
– 字元集編碼解碼解決方案。
– channel:一個新的原始i/o抽象。
– 支援鎖和記憶體映射檔案的檔案通路接口。
– 提供多路(non-bloking)非阻塞式的高伸縮性網絡i/o。
本文将圍繞這幾個特性進行學習和介紹。
buffer&chanel
channel和buffer是nio是兩個最基本的資料類型抽象。
buffer:
– 是一塊連續的記憶體塊。
– 是nio資料讀或寫的中轉地。
channel:
– 資料的源頭或者資料的目的地
– 用于向buffer提供資料或者讀取buffer資料,buffer對象的唯一接口。
– 異步i/o支援

圖1:channel和buffer關系
例子1:copyfile.java:
其中buffer内部結構如下(下圖拷貝自資料):
圖2:buffer内部結構
一個buffer主要由position,limit,capacity三個變量來控制讀寫的過程。此三個變量的含義見如下表格:
buffer常見方法:
flip():寫模式轉換成讀模式
rewind():将position重置為0,一般用于重複讀。
clear():清空buffer,準備再次被寫入(position變成0,limit變成capacity)。
compact():将未讀取的資料拷貝到buffer的頭部位。
mark()、reset():mark可以标記一個位置,reset可以重置到該位置。
buffer常見類型:bytebuffer 、mappedbytebuffer 、charbuffer 、doublebuffer 、 floatbuffer 、 intbuffer 、 longbuffer 、 shortbuffer。
channel常見類型:filechannel、datagramchannel(udp)、socketchannel(tcp)、serversocketchannel(tcp)
在本機上面做了個簡單的性能測試。我的筆記本性能一般。(具體代碼可以見附件。見nio.sample.filecopy包下面的例子)以下是參考資料:
– 場景1: copy一個370m的檔案
– 場景2: 三個線程同時拷貝,每個線程拷貝一個370m檔案
nio.charset
字元編碼解碼:位元組碼本身隻是一些數字,放到正确的上下文中被正确被解析。向bytebuffer中存放資料時需要考慮字元集的編碼方式,讀取展示bytebuffer資料時涉及對字元集解碼。
java.nio.charset提供了編碼解碼一套解決方案。
以我們最常見的http請求為例,在請求的時候必須對請求進行正确的編碼。在得到響應時必須對響應進行正确的解碼。
以下代碼向baidu發一次請求,并擷取結果進行顯示。例子示範到了charset的使用。
例子2baidureader.java
非阻塞 io
關于非阻塞io将從何為阻塞、何為非阻塞、非阻塞原理和異步核心api幾個方面來了解。
何為阻塞?
一個常見的網絡io通訊流程如下:
圖3:網絡通訊基本過程
從該網絡通訊過程來了解一下何為阻塞:
在以上過程中若連接配接還沒到來,那麼accept會阻塞,程式運作到這裡不得不挂起,cpu轉而執行其他線程。
在以上過程中若資料還沒準備好,read會一樣也會阻塞。
阻塞式網絡io的特點:多線程處理多個連接配接。每個線程擁有自己的棧空間并且占用一些cpu時間。每個線程遇到外部為準備好的時候,都會阻塞掉。阻塞的結果就是會帶來大量的程序上下文切換。且大部分程序上下文切換可能是無意義的。比如假設一個線程監聽一個端口,一天隻會有幾次請求進來,但是該cpu不得不為該線程不斷做上下文切換嘗試,大部分的切換以阻塞告終。
何為非阻塞?
下面有個隐喻:
一輛從a開往b的公共汽車上,路上有很多點可能會有人下車。司機不知道哪些點會有哪些人會下車,對于需要下車的人,如何處理更好?
1.司機過程中定時詢問每個乘客是否到達目的地,若有人說到了,那麼司機停車,乘客下車。(類似阻塞式)
2.每個人告訴售票員自己的目的地,然後睡覺,司機隻和售票員互動,到了某個點由售票員通知乘客下車。 (類似非阻塞)
很顯然,每個人要到達某個目的地可以認為是一個線程,司機可以認為是cpu。在阻塞式裡面,每個線程需要不斷的輪詢,上下文切換,以達到找到目的地的結果。而在非阻塞方式裡,每個乘客(線程)都在睡覺(休眠),隻在真正外部環境準備好了才喚醒,這樣的喚醒肯定不會阻塞。
非阻塞的原理
把整個過程切換成小的任務,通過任務間協作完成。
由一個專門的線程來處理所有的io事件,并負責分發。
事件驅動機制:事件到的時候觸發,而不是同步的去監視事件。
線程通訊:線程之間通過wait,notify等方式通訊。保證每次上下文切換都是有意義的。減少無謂的程序切換。
以下是異步io的結構:
圖4:非阻塞基本原理
reactor就是上面隐喻的售票員角色。每個線程的處理流程大概都是讀取資料、解碼、計算處理、編碼、發送響應。
異步io核心api
selector
異步io的核心類,它能檢測一個或多個通道(channel)上的事件,并将事件分發出去。
使用一個select線程就能監聽多個通道上的事件,并基于事件驅動觸發相應的響應。而不需要為每個channel去配置設定一個線程。
selectionkey
包含了事件的狀态資訊和時間對應的通道的綁定。