輕鬆 Java IO

NO IMAGE

      android 檔案 IO,當然也是 Java IO。廖廖幾百字,來說清楚檔案 IO 到底是什麼。通過對一些基本概念的理解以及 Java IO 中其他的一些必要性知識的理解,來透徹的理解 Java IO.

1.位元組,字元,與字元編碼與檔案編碼的關係

位元組:程式碼裡表示就是 byte

字元:程式碼裡單獨表示就是 char,byte 不能表示一個字元。

字元編碼:字元要用多少個位元組表示,以包括如何表示。就是傳說中的 utf-8,ansi,utf-16be,gbk 等

檔案編碼:也就是檔案儲存時採用何種字元編碼。儲存時採用何種編碼,那讀取時也必須採用相應的編碼。否則在碰到非英文字元的時候,就會碰到不懷好意的亂碼問題。

理解了上面的概念,也就是理解了,為什麼 Java 的 IO 會區分位元組流與字元流了。

2.一般的流

      可以這麼說,最基本的位元組流分別是 讀 – FileInputStream(FileWriter) ,寫 – FileOutputStream (FileReader)。這兩個類可以理解是真正與 “檔案 – File” 發生關係的。這兩個類中包含了更底層的 IO 處理,例如呼叫 IoBridge.open(),IoBridge.read(),IoBrige.write()以及IoBridge.closeAndSignalBlockedThreads()等橋接方法呼叫。而這些底層的類又是通過更底層的 Posix 類,再通過 JNI 呼叫到 C 層實現(C 層並沒有用 C語言的fread()等函式,而是直接呼叫的系統呼叫 read()),最後實現檔案的讀與寫等檔案相關的操作。所以流是對底層IO相關的系統呼叫的一個細節封裝。

    Java中定義了流的概念,輸入流或者輸出流,且不可逆不可任意跳轉,從原始碼上來看都是出於設計上的考量。參考 RandomAccessFile 的實現,其之所以能夠隨機訪問檔案,其實就是通過呼叫了 Libcore.os.lseek() 來實現。當然,普通的流也有呼叫到 Libcore.os.lseek(),就是 skip()方法,而沒有像 RandomAccessFile 一樣提供了上層的 seek() 方法。

3.裝飾的流

    為了大家使用上的方便,如讀一個 int 需要分成 4 個位元組去讀,並且做一次計算,Java中提供了 DataInputStream/DataOutputStream,其就是裝飾了一下,底層的檔案 IO 操作均是由 FileInputStream/FileOutStream 來實現的。

public final int readInt() throws IOException {
387        // b/30268192
388        // Android-changed: Use read(byte[], int, int) instead of read().
389        readFully(readBuffer, 0, 4);//讀 4 個位元組
390        return Memory.peekInt(readBuffer, 0, ByteOrder.BIG_ENDIAN);//將 4 個位元組轉換成 Int 值
391    }
/**
* 分大小端轉成 Int
**/
public static int peekInt(byte[] src, int offset, ByteOrder order) {
46        if (order == ByteOrder.BIG_ENDIAN) {
47            return (((src[offset  ] & 0xff) << 24) |
48                    ((src[offset  ] & 0xff) << 16) |
49                    ((src[offset  ] & 0xff) <<  8) |
50                    ((src[offset  ] & 0xff) <<  0));
51        } else {
52            return (((src[offset  ] & 0xff) <<  0) |
53                    ((src[offset  ] & 0xff) <<  8) |
54                    ((src[offset  ] & 0xff) << 16) |
55                    ((src[offset  ] & 0xff) << 24));
56        }
57    }

BufferedInputStream/BufferedOutputStream 就更簡單了,就是在 IO 前先將資料存入或者寫出到byte[] buffer 中,外加一個 count 計數器記錄下寫入與寫出的位元組個數。而真正與檔案發生 IO 的還是 FileInputStream/FileOutputStream

BufferedWriter/BufferedReader 是類似的,只不過它的 buf 是 char[] ,而不是 byte[]。同時,它也用了一個 pos 作為下標計數器。

4.PipedInputStream/PipedOutputStream

    管道流,單獨放在最後講,並不是因為它有多麼的高大上,因為慚愧的是,我在實際編碼當中從來沒有用到過。但這兩個類所涉及的設計思想非常值得我們學習。

作用:用於兩個不同的執行緒之間進行通訊

檔案IO:這兩個類是沒有檔案 IO 的。寫入讀出都是在同一個 byte[] buffer 內

封裝:PipedOutputStream 封裝了 PIpedInputStream,PipedInputStream 封裝了 byte[] buffer

buffer:這是一個迴圈 buffer ,分別用了 in 表示下一個要寫的位置,out 表示下一個要讀的位置。讀的最大範圍為 [out,int) 左閉右開區間,寫的最大範圍為 [in,out)

    in 為 -1 表示空 buffer,讀執行緒阻塞等待

    in == empty 表示 buffer 滿,寫執行緒阻塞等待

但需要注意的是,不管是讀待還是寫等待,最多隻會等 3 秒鐘,且是每隔一次一秒的。

小結

    Java 中還有一些其他相關的流,如 PrintStream/PrintWriter ,其也就是多了一些封裝幫助我們更方便更快捷的完成程式碼編寫,從而摒棄一些複雜的運算,專心處理業務邏輯即可。

    Java IO 的知識點還很多,上面都是一些最基礎的東西,但掌握了上面這些比較基礎的東西,那其他複雜的東西也就容易掌握了。

    Java 還有一類 IO 稱為 NIO,其主要目的是為了超大檔案,複用IO的非同步讀寫。NIO 的三要素為 Channel,Buffer,Selector。先知道有這麼幾個東西吧。