Java基礎梳理之-IO操做

回想最開始學習Java IO相關的操做時, 被各類Reader/Stream繞暈。 如今再回頭梳理這一塊的知識點,感受清晰了不少。 Java做爲編程語言,
大部分東西都是從系統層面帶來的, 因此學習的知識點雖然在Java, 可是背後的答案卻在操做系統層面。編程

首先理解核心概念:IO, 究竟什麼是IO? 所謂IO就是內存與外設相關的數據傳輸。經常使用的外設有硬盤,網卡,打印機, 鼠標...
咱們接觸最頻繁的IO操做是硬盤上文件的讀寫,因此學習IO基本上都是以文件操做爲例子。IO做爲操做系統的核心,知識點至關龐雜,若是沒有合適的切入點,容易迷失其中。 vim

若是必定要找一個切入點,學習的前後順序,我的建議以下:windows

  1. RandomAccessFile

對於文件的操做,最適合是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有以下的幾個優勢:

  1. channel是支持讀寫的,因此相比Stream更靈活。
  2. buffer能夠分配堆外內存,這個對於IO來講,避免了數據從堆內存中倒騰一邊,也避免了Java的GC, 性能天然有提高。
  3. 對於網絡IO, NIO能夠在同一個線程同時監聽多個端口,避免了建立多個線程和線程管理的開銷。

因爲IO這一塊的知識點過於龐雜,不是一篇博客能說清楚的,這裏只是簡單梳理一下學習思路。

相關文章
相關標籤/搜索