簡介: I/O 問題能夠說是當今互聯網 Web 應用中所面臨的主要問題之一,由於當前在這個海量數據時代,數據在網絡中隨處流動。這個流動的過程當中都涉及到 I/O 問題,能夠說大部分 Web 應用系統的瓶頸都是 I/O 瓶頸。本文的目的正是分析 I/O 的內在工做機制,你將瞭解到:Java 的 I/O 類庫的基本架構;磁盤 I/O 工做機制;網絡 I/O 的工做機制;其中以網絡 I/O 爲重點介紹 Java Socket 的工做方式;你還將瞭解到 NIO 的工做方式,還有同步和異步以及阻塞與非阻塞的區別,最後咱們將介紹一些經常使用的關於 I/O 的優化技巧。java
I/O 問題是任何編程語言都沒法迴避的問題,能夠說 I/O 問題是整我的機交互的核心問題,由於 I/O 是機器獲取和交換信息的主要渠道。在當今這個數據大爆炸時代,I/O 問題尤爲突出,很容易成爲一個性能瓶頸。正因如此,因此 Java 在 I/O 上也一直在作持續的優化,如從 1.4 開始引入了 NIO,提高了 I/O 的性能。關於 NIO 咱們將在後面詳細介紹。網絡
Java 的 I/O 操做類在包 java.io 下,大概有將近 80 個類,可是這些類大概能夠分紅四組,分別是:架構
基於字節操做的 I/O 接口:InputStream 和 OutputStreamapp
基於字符操做的 I/O 接口:Writer 和 Reader異步
基於磁盤操做的 I/O 接口:File編程語言
基於網絡操做的 I/O 接口:Socket性能
前兩組主要是根據傳輸數據的數據格式,後兩組主要是根據傳輸數據的方式,雖然 Socket 類並不在 java.io 包下,可是我仍然把它們劃分在一塊兒,由於我我的認爲 I/O 的核心問題要麼是數據格式影響 I/O 操做,要麼是傳輸方式影響 I/O 操做,也就是將什麼樣的數據寫到什麼地方的問題,I/O 只是人與機器或者機器與機器交互的手段,除了在它們可以完成這個交互功能外,咱們關注的就是如何提升它的運行效率了,而數據格式和傳輸方式是影響效率最關 鍵的因素了。咱們後面的分析也是基於這兩個因素來展開的。優化
基於字節的 I/O 操做接口輸入和輸出分別是:InputStream 和 OutputStream,InputStream 輸入流的類繼承層次以下圖所示:
圖 1. InputStream 相關類層次結構(查看大圖)
輸入流根據數據類型和操做方式又被劃分紅若干個子類,每一個子類分別處理不一樣操做類型,OutputStream 輸出流的類層次結構也是相似,以下圖所示:
圖 2. OutputStream 相關類層次結構(查看大圖)
這裏就不詳細解釋每一個子類如何使用了,若是不清楚的話能夠參考一下 JDK 的 API 說明文檔,這裏只想說明兩點,一個是操做數據的方式是能夠組合使用的,如這樣組合使用
OutputStream out = new BufferedOutputStream(new ObjectOutputStream(new FileOutputStream("fileName"))
;
還有一點是流最終寫到什麼地方必需要指定,要麼是寫到磁盤要麼是寫到網絡中,其實從上面的類圖中咱們發現,寫網絡實際上也是寫文件,只不過寫網絡還 有一步須要處理就是底層操做系統再將數據傳送到其它地方而不是本地磁盤。關於網絡 I/O 和磁盤 I/O 咱們將在後面詳細介紹。
不論是磁盤仍是網絡傳輸,最小的存儲單元都是字節,而不是字符,因此 I/O 操做的都是字節而不是字符,可是爲啥有操做字符的 I/O 接口呢?這是由於咱們的程序中一般操做的數據都是以字符形式,爲了操做方便固然要提供一個直接寫字符的 I/O 接口,如此而已。咱們知道字符到字節必需要通過編碼轉換,而這個編碼又很是耗時,並且還會常常出現亂碼問題,因此 I/O 的編碼問題常常是讓人頭疼的問題。關於 I/O 編碼問題請參考另外一篇文章 《深刻分析Java中的中文編碼問題》。
下圖是寫字符的 I/O 操做接口涉及到的類,Writer 類提供了一個抽象方法 write(char cbuf[], int off, int len) 由子類去實現。
圖 3. Writer 相關類層次結構(查看大圖)
讀字符的操做接口也有相似的類結構,以下圖所示:
圖 4.Reader 類層次結構(查看大圖)
讀字符的操做接口中也是 int read(char cbuf[], int off, int len),返回讀到的 n 個字節數,不論是 Writer 仍是 Reader 類它們都只定義了讀取或寫入的數據字符的方式,也就是怎麼寫或讀,可是並無規定數據要寫到哪去,寫到哪去就是咱們後面要討論的基於磁盤和網絡的工做機 制。
另外數據持久化或網絡傳輸都是以字節進行的,因此必需要有字符到字節或字節到字符的轉化。字符到字節須要轉化,其中讀的轉化過程以下圖所示:
InputStreamReader 類是字節到字符的轉化橋樑,InputStream 到 Reader 的過程要指定編碼字符集,不然將採用操做系統默認字符集,極可能會出現亂碼問題。StreamDecoder 正是完成字節到字符的解碼的實現類。也就是當你用以下方式讀取一個文件時:
try { StringBuffer str = new StringBuffer(); char[] buf = new char[1024]; FileReader f = new FileReader("file"); while(f.read(buf)>0){ str.append(buf); } str.toString(); } catch (IOException e) {} |
FileReader 類就是按照上面的工做方式讀取文件的,FileReader 是繼承了 InputStreamReader 類,其實是讀取文件流,而後經過 StreamDecoder 解碼成 char,只不過這裏的解碼字符集是默認字符集。
寫入也是相似的過程以下圖所示:
經過 OutputStreamWriter 類完成,字符到字節的編碼過程,由 StreamEncoder 完成編碼過程。