【原創】從原始碼剖析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進行回收了。