InputStream類詳解


  InputStream這個抽象類是所有基於字節的輸入流的超類,抽象了Java的字節輸入模型。在這個類中定義了一些基本的方法。看一下類的定義:

public abstract class InputStream implements Closeable

  首先這是一個抽象類,實現了Closeable接口,也Closeable接口又拓展了AutoCloseable接口,因此所有InputStream及其子類都可以用於Java 7 新引入的帶資源的try語句。讀入字節之前,我們可能想要先知道還有多少數據可用,這有available方法完成,具體的讀入由read()及其重載方法完成,skip方法用於跳過某些字節,同時定義了幾個有關標記(mark)的方法,讀完數據使用close方法關閉流,釋放資源。下面詳細介紹各個方 法:

1. available方法

public int available() throws IOException  

  假設方法返回的int值為a,a代表的是在不阻塞的情況下,可以讀入或者跳過(skip)的字節數。也就是說,在該對象下一次調用讀入方法讀入a個字節,或者skip方法跳過a個字節時,不會出現阻塞(block)的情況。這個調用可以由相同線程調用,也可以是其他線程調用。但是在下次讀入或跳過的時候,實際讀入(跳過)的可能不只a字節。當遇到流結尾的時候,返回0。如果出現I/O錯誤,拋出IOException異常。看一下InputStream中該方法的實現:

public int available() throws IOException {  
      return 0;  
}  
  只是簡單地返回0,因此子類必須重寫該方法。注意到一點,雖然這個方法實現中根本不會出現異常,但是還是在throws中指出(specify)可能拋出 IOException。這是Java異常機制很重要的一個點,子類的方法不能throws父類方法沒有throws的異常(構造器除外),因此在父類方法先指出,然后允許子類方法拋出IOException。 單獨使用這一方法幾乎沒有意義,它一般用於在讀入或者跳過之間先探測一下有多少可用字節。

2. 讀入方法:read

  跟讀入相關的方法是這個類的核心方法。有3種重載的形式,下面分別介紹。

2.1 read()

public abstract int read()throws IOException  
  讀取輸入流的下一個字節這是一個抽象方法,不提供實現,子類必須實現這個方法。該方法讀取下一個字節,返回一個0-255之間的int類型整數。如果到達流的末端,返回-1. 調用該方法的時候,方法阻塞直到出現下列其中一種情況:1)遇到流的尾部(end of the stream)。2)有數據可以讀入。3)拋出異常 面向字節的操作時,可能需要像這樣比較底層的字節操作。我們也可以一次讀入多個字節,使用下面的重載形式。

2.2 read(byte[] b)

public int read(byte b[]) throws IOException  
  試圖讀入多個字節,存入字節數組b,返回實際讀入的字節數。如果傳遞的是一個空數組(注意數組長度可以為0,即空數組。比如 byte[] b = new byte[0]; 或者byte[] b = {};)那么什么也沒讀入,返回0.
  如果到達流尾部,沒有字節可讀,返回-1;如果上面兩種情況都沒有出現,並且沒有I/O錯誤,則至少有1個字節被讀入,存儲到字節數組b中。實際讀入的第一個字節存在b[0],往后一次存入數組,讀入的字節數最多不能超過數組b的長度。如果讀入的字節數小於b的長度,剩余的數組元素保持不變。具體地,如果讀入的字節數為k,則k個字節分別存在 b[0]到b[k-1],而b[k]到b[b.length-1]保持原來的數據。

2.3 read (byte[] b, int off, int len)

public int read(byte[] b,int off,int len) throws IOException  
  這個方法跟上一個功能類似,除了讀入的數據存儲到b數組是從off開始。len是試圖讀入的字節數,返回的是實際讀入的字節數。如果len=0,則什么也不讀入,返回0;如果遇到流尾部,返回-1.否則至少讀入一個字節。
假設實際讀入k個字節,則k個字節分別存儲在b[off]到b[off+k-1],而b[off+k]往后的元素保持不變。b[off]之前也保持不變。
public int read(byte b[]) throws IOException {  
    return read(b, 0, b.length);  
} 
   解析來看一下第三個read方法的源代碼:
 public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {   // 檢測參數是否為null
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException(); // 數組越界檢測
        } else if (len == 0) {
            return 0;   //如果b為空數組,返回0
        }

        int c = read(); // 調用read()方法獲取下一個字節
        if (c == -1) {
            return -1;
        }               // 遇到流尾部,返回-1
        b[off] = (byte)c;  //讀入的第一個字節存入b[off]

        int i = 1;    // 統計實際讀入的字節數
        try {
            for (; i < len ; i++) { // 循環調用read,直到流尾部
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c; // 一次存入字節數組
            }
        } catch (IOException ee) {
        }
        return i;  // 返回實際讀入的字節數
    }
View Code
  我們看到方法可能拋出IOException異常,如果第一個字節無法讀入且原因不是到達流尾部,或者流已經被關閉,或者其他IO錯誤,則拋出這個異常。
  上面三個讀入方法都可能出現阻塞,在2.1中已經介紹了阻塞解除的條件。理解這三個方法很重要的一點是:方法只是嘗試讀入我們想要的字節數,但是能否成功, 會受到數據源的影響。另外一點就是讀入的數據存到哪里,第一個方法作為返回值,第二、三個方法存入到指定數組的指定位置,返回的是實際讀入的字節數。后兩個方法真正的讀入工作都是通過調用抽象方法read()來完成的,資格抽象方法在子類中實現。

3. skip方法

public long skip(long n)throws IOException  

  這個方法試圖跳過當前流的n個字節,返回實際跳過的字節數。如果n為負數,返回0.當然子類可能提供不能的處理方式。n只是我們的期望,至於具體跳過幾個,則不受我們控制,比如遇到流結尾。修改上面的例子:      

  跳過2個字節的時候位置由A跳到B,而你如果試圖一次跳過6個字節,你會發現它只能跳過5個字節到達C。最后來看一下這個方法在InputStream來中的實現:
 public long skip(long n) throws IOException {
        long remaining = n; // 還有多少字節沒跳過
        int nr;
        if (n <= 0) {
            return 0;  // n小於0 簡單返回0
        }
        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining); // 這里的常數在類中定義為2048
        byte[] skipBuffer = new byte[size]; // 新建一個字節數組,如果n<2048,數組大小為n,否則為2048
        while (remaining > 0) {
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining)); // 讀入字節,存入數組 
            if (nr < 0) {  // 遇到流尾部跳出循環
                break;
            }
            remaining -= nr;
        }
        return n - remaining;
}
View Code
  從代碼的邏輯上可以看出,是通過不斷地讀取字節來完成跳過的任務的。首先建立一個緩沖數組,這個數組的大小不超過2048.如果要跳過的字節大於2048, 則數組大小為2048,否則采用要跳過的字節數作為數組長度。接下來不斷讀取字節,填入數組,如果還沒跳過的字節數超過緩沖數組長度,則讀入2048,否 則讀入還沒跳過的字節,完成跳過任務。如果遇到流尾部,跳出循環,返回已經讀入的字節個數

4.與標記相關的方法

  與標注相關的方法使得我們可以重新讀入(跳過)那些已經被我們讀入或者跳過的字節,再重新來過一次。

4.1 mark

public void mark(int readlimit)  
   這個方法用於在流的當前位置做個標記,參數readLimit指定這個標記的“有效期“,如果從標記處開始往后,已經獲取或者跳過了readLimit個字節,那么這個標記失效,不允許再重新回到這個位置(通過reset方法)。也就是你想回頭不能走得太遠呀,浪子回頭不一定是岸了,跳過(獲取)了太多字 節,標記就不再等你啦。多次調用這個方法,前面的標記會被覆蓋。

  看一下上面的圖,如果我們在M出做標記,readLimit為綠色部分,當流的指針在A處的時候,這個標記依然有效,可是一旦指針跑到B處,標記就失效了。

4.2 reset

public void reset()throws IOException  
  這個方法用於重定位到最近的標記。如果在這之前mark方法從來沒被調用,或者標記已經無效,在拋出IOException。如果沒有拋出這個異常,將當前位置重新定位到最近的標記位置。
InputStream is = null;
try {
    is = new BufferedInputStream(new FileInputStream("test.txt"));
    is.mark(4);
    is.skip(2);
    is.reset();
   System.out.println((char)is.read()); } finally { if (is != null) { is.close(); } }
   我們使用了支持mark的BufferedInputStream,首先一開始做標記,跳過兩個自己,然后再回到最初的位置。

4.3 markSupported

public boolean markSupported()  

  檢測當前流對象是否支持標記。是返回true。否則返回false。比如InputStream不支持標記,而BufferedInputStream支持。

5. close方法

public void close()throws IOException  

  關閉當前流,釋放與該流相關的資源,防止資源泄露。在帶資源的try語句中將被自動調用。關閉流之后還試圖讀取字節,會出現IOException異常。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM