【原創】從源碼剖析IO流(一)輸入流與輸出流--轉載請註明出處

InputStream與OutPutStream兩個抽象類,是所有的流的基礎,首先來看這兩個流的API

InputStream:

public abstract int read() throws IOException; 從輸入流中讀取數據的下個字節。
public int read(byte b[]) throws IOException{…} 從輸入流中讀取一定數量的字節,並將其存儲在緩衝區數組b中。
public int read(byte b[], int off, int len) throws IOException{…} 將輸入流中最多len個數據字節讀入byte數組。
public long skip(long n) throws IOException{…} 跳過和丟棄此輸入流中數據的n個字節。
public int available() throws IOException{…} 返回此輸入流下一個方法調用可以不受阻塞地從此輸入流讀取(或跳過)的估計字節數。
public void close() throws IOException {} 關閉此輸入流並釋放與該流關聯的所有系統資源。
public synchronized void mark(int readlimit) {} 在此輸入流中標記當前的位置。對reset方法的後續調用會在最後標記的位置重新定位此流,以便後續讀取重新讀取相同的字節。
public synchronized void reset() throws IOException {…} 將此流重新定位到最後一次對此輸入流調用mark方法時的位置。
public boolean markSupported() {…} 測試此輸入流是否支持mark和reset方法。

OutPutStream

方法 說明
public abstract void write(int b) throws IOException; 將指定的字節寫入此輸出流。
public void write(byte b[]) throws IOException {…} 將b.length個字節從指定的byte數組寫入此輸出流。
public void write(byte b[], int off, int len) throws IOException {…} 將指定byte數組中從偏移量off開始的len個字節寫入此輸出流。
public void flush() throws IOException {} 刷新此輸出流並強制寫出所有緩衝的輸出字節。
public void close() throws IOException {} 關閉此輸出流並釋放與此流有關的所有系統資源。

這兩個類的方法如上,可以看到在這兩個方法中,分別具有一個抽象方法,分別爲read()和write()方法。這兩個方法便是整個IO體系中的核心方法,所有的內容均由這兩個方法爲基礎進行實現。然後我們來看一下主要的兩個比較主要的read(byte b[], int off, int len) 方法和write(byte b[], int off, int len)方法的實現:

/**
 * 將輸入流中最多len個字節讀入byte數組。嘗試讀取len個字節,但讀取的字節也可能小於該值。以整數形式返回實際讀取的字節數。
 * 
 * 在輸入數據可用、檢測到流末尾或者拋出異常前,此方法一直阻塞。
 * 
 * 如果len爲0,則不讀取任何字節並返回0;否則,嘗試讀取至少一個字節。如果因爲流位於文件末尾而沒有可用的字節,則返回值-1;否則,至少讀取一個字節並將其存儲在b中。
 *
 * 將讀取的第一個字節存儲在元素b[off]中,下一個存儲在 b[off+1] 中,依次類推。讀取的字節數最多等於len。設k爲實際讀取的字節數;這些字節將存儲在b[off]到b[off+k-1]的元素中,不影響b[off+k]到b[off+len-1]的元素。
 * 
 * 在任何情況下,b[0]到b[off]的元素以及b[off+len]到b[b.length-1] 的元素都不會受到影響。
 * 
 * 類InputStream的read(b, off, len)方法重複調用方法read()。如果第一次這樣的調用導致IOException,則從對read(b, off, len)方法的調用中返回該異常。如果對read()的任何後續調用導致IOException,則捕獲該異常並將其視爲到達文件末尾;到達該點時讀取的字節存儲在b中,並返回發生異常之前讀取的字節數。在已讀取輸入數據len的請求數量、檢測到文件結束標記、拋出異常前,此方法的默認實現將一直阻塞。建議子類提供此方法更爲有效的實現。
 * 
 * @param      b     讀入數據的緩衝區。
 * @param      off   數組 b 中將寫入數據的初始偏移量。
 * @param      len   要讀取的最大字節數。
 * @return     讀入緩衝區的總字節數;如果因爲已到達流末尾而不再有數據可用,則返回-1。
 * @exception  IOException 如果不是因爲位於文件末尾而無法讀取第一個字節;如果輸入流已關閉;如果發生其他 I/O 錯誤。
 * @exception  NullPointerException  如果 b 爲 null。
 * @exception  IndexOutOfBoundsException 如果off爲負,len爲負,或者len大於b.length - off
 * @see        java.io.InputStream#read()
 */
public int read(byte b[], int off, int len) throws IOException {
    //檢查參數是否合法,不合法拋出異常
    if (b == null) {
        throw new NullPointerException();
    } else if (off < 0 || len < 0 || len > b.length - off) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return 0;
    }
    // 讀取一個字節
    int c = read();
    // 如果因爲流位於文件末尾而沒有可用的字節,返回-1
    if (c == -1) {
        return -1;
    }
    // 將讀取的第一個字節存儲在元素b[off]中
    b[off] = (byte)c;

    int i = 1;
    // 繼續往下讀取
    try {
        for (; i < len ; i++) {
            c = read();
            if (c == -1) {
                break;
            }
            b[off + i] = (byte)c;
        }
    } catch (IOException ee) {
    }
    return i;
}
/**
 * 將指定byte數組中從偏移量off開始的len個字節寫入此輸出流。
 * 
 * write(b, off, len) 的常規協定是:
 * 將數組b中的某些字節按順序寫入輸出流;
 * 元素b[off]是此操作寫入的第一個字節,b[off+len-1]是此操作寫入的最後一個字節。
 * 
 * OutputStream 的write方法對每個要寫出的字節調用一個參數的write 方法。
 * 建議子類重寫此方法並提供更有效的實現。
 *
 * @param      b     數據。
 * @param      off   數據中的初始偏移量。
 * @param      len   寫入的字節數。
 * @exception  IOException   如果發生I/O錯誤。尤其是關閉了輸出流。
 */
public void write(byte b[], int off, int len) throws IOException {
    //檢查參數是否合法
    if (b == null) {
        throw new NullPointerException();
    } else if ((off < 0) || (off > b.length) || (len < 0) ||
               ((off + len) > b.length) || ((off + len) < 0)) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return;
    }
    // 將指定byte數組中從偏移量off開始的len個字節寫入此輸出流。
    for (int i = 0 ; i < len ; i++) {
        write(b[off + i]);
    }
}

從上方兩個代碼片段中,可以看出在進行讀取流時,操作時非常簡單的,只是調用write方法與read方法進行處理流信息。分別爲將read出來的字節,寫入到byte[]的某位中,將一個byte[]字節中的某位,寫入到流中。

此時,我們再看一段比較有意思的代碼,這段代碼是InputStream中的skip()方法:

/**
 * 跳過和丟棄此輸入流中數據的n個字節。出於各種原因,skip方法結束時跳過的字節數可能小於該數,也可能爲0。導致這種情況的原因很多,跳過n個字節之前已到達文件末尾只是其中一種可能。返回跳過的實際字節數。如果n爲負,方法返回0,,不跳過任何字節。子類可能對負值有不一樣的處理。
 *
 * skip方法創建一個byte數組,然後重複將字節讀入其中,直到讀夠n個字節或已到達流末尾爲止。建議子類提供此方法更爲有效的實現。例如,可依賴搜索能力的實現。
 *
 * @param      n   要跳過的字節數。
 * @return     實際跳過的字節數
 * @exception  IOException  如果流不支持搜索,或者發生其他 I/O 錯誤。
 */
public long skip(long n) throws IOException {

    long remaining = n;
    int nr;
    // 如果n<=0,返回0
    if (n <= 0) {
        return 0;
    }

    int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
    // 創建一個byte數組,然後重複將字節讀入其中,直到讀夠n個字節或已到達流末尾爲止。
    byte[] skipBuffer = new byte[size];
    while (remaining > 0) {
        nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
        if (nr < 0) {
            break;
        }
        remaining -= nr;
    }

    return n - remaining;
}

這個方法的用途爲,將接下來的字節丟棄,在這個方法中,進行的操作爲新創建一個空的byte[],然後將這N個字節的信息,讀取到這個byte[]中,然後將這個byte[]棄之一邊,等待GC進行回收。

然後,我們要注意一下的是,默認InputStream與OutPutStream的源碼中,close()方法的實現都是空的,包括輸出流中的close()方法。

總結:在InputStream與OutPutStream中,爲我們提供了一些基本的write和read的方法的封裝,但是這些方法都是依賴於抽象的read()方法與write()方法的。同時,還可以注意到,在進行InputStream的讀取時,如果想要跳過若干個字節,這裏的處理實際上是對這若干個字節進行了讀取,只是沒有返回給上層方法,沒有對允許這部分的字節進行任何的操作,直接是在等待GC進行回收了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章