回想最開始學習Java IO相關的操做時, 被各類Reader/Stream繞暈。 如今再回頭梳理這一塊的知識點,感受清晰了不少。 Java做爲編程語言,
大部分東西都是從系統層面帶來的, 因此學習的知識點雖然在Java, 可是背後的答案卻在操做系統層面。編程
首先理解核心概念:IO, 究竟什麼是IO? 所謂IO就是內存與外設相關的數據傳輸。經常使用的外設有硬盤,網卡,打印機, 鼠標...
咱們接觸最頻繁的IO操做是硬盤上文件的讀寫,因此學習IO基本上都是以文件操做爲例子。IO做爲操做系統的核心,知識點至關龐雜,若是沒有合適的切入點,容易迷失其中。 vim
若是必定要找一個切入點,學習的前後順序,我的建議以下:windows
對於文件的操做,最適合是RandomAccessFile: 它能讀能寫能定位。 使用RandomAccessFile, 就是把文件看成一個數組,只不過這個數組是在硬盤上而已。讀寫文件就像操做數組同樣。更難能難得的是, RandomAccessFile封裝了Java全部的基礎類型, 能夠說基本知足操做單個文件的使用需求了。數組
public static void main(String[] args) throws IOException { RandomAccessFile bigArray = new RandomAccessFile(new File("/home/shgy/a.txt"),"rw"); // 寫 bigArray.seek(10); bigArray.writeUTF("hello,world"); System.out.println("filePointer at " + bigArray.getFilePointer()); // 讀 bigArray.seek(10); System.out.println(bigArray.readUTF()); bigArray.close(); }
RandomAccessFile類有14個寫的方法,17個讀的方法,2個定位方法,2個長度相關操做,1個獲取當前文件遊標getFilePointer()
的方法, 1個關閉文件釋放系統資源的方法。 剩下的兩個方法getChannel()
和 getFD()
。getChannel()
跟NIO相關,getFD()
是系統文件描述符,就本文所要總結的內容而言,已經超出三界以外,不在五行之中,暫時略過不提。安全
當文件數量多了之後,必然面臨管理的問題。不管是windows仍是Linux都採用層級管理,最後造成目錄樹。這帶來一個新的問題就是文件的路徑以及文件的歸類。 面對這樣的需求,Java提供了Files類來解決。經過了解Files類提供的API, 能夠看出,其功能特色在於粗粒度的文件讀寫及文件屬性的管理。
使用Files來讀寫文件更簡單:網絡
public static void main(String[] args) throws IOException { Files.write("hello world", new File("/home/shgy/a.txt"),Charset.defaultCharset()); System.out.println(Files.readLines(new File("/home/shgy/a.txt"), Charset.defaultCharset())); }
Files 能夠讀寫文件,能夠重命名文件,能夠讀取設置文件屬性,簡直是瑞士×××般的存在。這裏涉及了更多文件相關的知識點,若是有學習過《鳥哥的Linux私房菜》第七章,再學習代碼操做文件,就不會那麼困惑了。 dom
使用Files操縱文件引出了一個新的知識點Charset
, 即字符集。字符集產生的緣由很簡單: 人類語言是字符形式,計算機只能以字節的方式存儲數據,字符跟字節之間得有個映射關係。好比上例中存儲的hello world
, 實際上存儲的內容可使用vim的xdd
命令查看:異步
// vim + %!xdd 命令便可 00000000: 6865 6c6c 6f20 776f 726c 640a hello world.
關於字符集的知識,能夠參考阮一峯的《字符編碼筆記:ASCII,Unicode 和 UTF-8》。編程語言
理解了字符集,再進入Java的IO模塊,才瓜熟蒂落。前面已經說過,所謂IO,就是內存與計算機外設的數據傳輸。Java從語言層面對IO進行了抽象, 這個抽象就是Steam, 數據流。這樣的話,不管數據來源是文件,網頁,內存塊仍是其餘,都以一種統一的視角和處理方式看待。 因此Java定義了InputStream和OutputStream。
InputStream用於將數據讀入內存, 對應的操做是read; OutputStream用於將數據寫入外設,對應的操做是write。InputStream和OutputStream操縱的數據只能是字節或者字節數組, 這樣就不用關心數據是文本,圖片,音頻,視頻了,畢竟無論什麼類型的數據,最終的呈現形式就是字節流。
這樣,文件的操做就至關繁瑣了:ide
public static void main(String[] args) throws IOException { // 讀取文件 FileInputStream fis = new FileInputStream(new File("/home/shgy/a.txt")); byte[] bytes = new byte[1024]; int n = fis.read(bytes); if(n>0){ System.out.println(new String(bytes,0,n)); } fis.close(); // 寫文件 FileOutputStream fos = new FileOutputStream(new File("/home/shgy/a.txt")); fos.write("hello, world".getBytes()); fos.close(); }
鑑於咱們處理的文件,絕大部分都是字符類型的文件,並且以字節的方式操縱字符確實過於原始,因而Java也定義了字符IO, 即Reader/Writer。
public static void main(String[] args) throws IOException { // 讀取文件 FileReader fr = new FileReader(new File("/home/shgy/a.txt")); char[] buf = new char[1024]; int n = fr.read(buf); System.out.println(new String(buf,0,n)); fr.close(); FileWriter fw = new FileWriter(new File("/home/shgy/a.txt")); fw.write("hello, world"); fw.close(); }
因爲計算機本質是處理字節,因此字符和字節之間須要一個橋樑,這個就是InputStreamReader/OutputStreamWriter. 爲了應對各類字符集和字節之間的編碼解碼,因此定義了StreamEncoder/StreamDecoder。
對於文件的讀寫,因爲是須要操做硬盤或者網卡;考慮到安全性, 在系統層面須要系統調用,由用戶態切入內核態。這個操做代價較高, 因此又添加了一層緩衝,將原來須要屢次請求的IO合併到一塊兒,即BufferedInputStream/BufferedOutputStream 和 BufferedReader/BufferedWriter。
整個IO操做在InputStream/OutputStream和Reader/Writer基礎之上豐富多彩起來。
因爲外設,好比硬盤和網絡數據的傳輸效率相比CPU的處理效率相差太遠, 在《性能之顛》中有這樣一個讓人影響深入的對比:
1個CPU週期爲0.3ns, 1次機械磁盤IO週期爲1~10ms, 1次從舊金山到紐約的互聯網傳輸須要40ms; 因爲時間單位過小,咱們沒有概念。 咱們放大一下,假如:
1個CPU週期爲1s, 則一次機械磁盤IO週期爲1~12個月,1次從舊金山到紐約的互聯網傳輸須要4年。 在這樣一個差距面前,如何提升IO的效率,就顯得尤其重要,
這就是NIO的由來。
在《UNIX網絡編程卷1:套接字聯網API》一書中總結了5種IO模型: 阻塞,非阻塞,IO複用,信號驅動,異步IO。Java的NIO是採用了IO複用(select)模型。
NIO處理數據,方式跟Stream有所不一樣。 Stream比較碎,以字節爲最小粒度; NIO以數據塊爲最小粒度。因此能夠避免數據的反覆搬運,更高效,操做起來就更繁瑣一些。
// 使用NIO寫數據到文件 public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream(new File("/home/shgy/a.txt")); FileChannel fc = fos.getChannel(); ByteBuffer buf = ByteBuffer.allocate(1024); buf.put("hello,world".getBytes()); buf.flip(); fc.write(buf); System.out.println("file channel position is " + fc.position()); fos.close(); }
NIO有以下的幾個優勢:
因爲IO這一塊的知識點過於龐雜,不是一篇博客能說清楚的,這裏只是簡單梳理一下學習思路。