BufferedOutputStream的快取功能解析(原始碼閱讀)
要介紹BufferedOutputStream,我們先了解一下OutputStream類
抽象類OutputStream類有三個write方法
- public abstract void write(int b)
- public void write(byte b[])
- public void write(byte b[], int off, int len)
由上面我們可以看出第一個write方法是讓子類覆蓋的,而第二個人write(byte b[])方法原始碼如下
public void write(byte b[]) throws IOException {
write(b, 0 , b.length);
}
所以可見最後處理還是呼叫第三個方法write(byte b[],int off,int len),該方法原始碼如下:
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;
}
for (int i = 0 ; i < len ; i++) {
//注意這兒,這兒其實呼叫前面的抽象方法write(int b),同時還發生了自動轉型
write(b[off + i]);
}
}
問題
我們先不看抽象方法是如何實現的,也就是說OutputStream也具有快取器功能,我們可以將要寫入到流中的資料寫到一個byte[] buf陣列中,然後呼叫write(byte b[])或者write(byte b[], int off, int len)也可以,那為什麼還要BufferedInputStream類幹什麼呢,他們有什麼區別呢。同時我們知道BufferedInputStream類中還有一個flush()方法,在OutputStream流中沒有flush()方法,這又是為什麼呢?flush()是不是必須的呢,接下來看一下BufferedOutputStream類;
首先,BufferedOutput將OutputStream類物件作為一個構造方法的引數的。
首先看一下BufferedOutputStream 類原始碼
public
class BufferedOutputStream extends FilterOutputStream {
//這兒定義了一個byte[]陣列,用來充當快取器
protected byte buf[];
//這個變數是重點,他就是用來記錄當前快取器中的位元組數量的
protected int count;
//我們初始化建立一個物件的時候給了這個buf這個陣列8192個位元組.
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
// 這兒建立一個給定大小的陣列物件來充當快取器
buf = new byte[size];
}
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
//該方法是重點
public synchronized void write(byte b[], int off, int len) throws IOException {
//如果傳進來的陣列長度大於buf 陣列的長度,則直接呼叫OutputStream物件的write方法。
if (len >= buf.length) {
flushBuffer();
out.write(b, off, len);
return;
}
//驗證BufferedOutputStream 類中buf剩下的空間能否裝得下傳進來的陣列。如果不能則先將當前buf陣列中資料寫入底層io流中
if (len > buf.length - count) {
flushBuffer();
}
//該處是重點,如果在當前BufferedOutputStream 類中buf陣列沒有滿,則將傳進來的陣列複製到當前類物件buf陣列中,同時更新count的值。
System.arraycopy(b, off, buf, count, len);
count += len;
//呼叫flushBuffer方法也就是將不滿8192個位元組陣列中的資料傳送出去。同時將count置零。
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
//強制將buf資料中未滿8192個位元組的資料寫入底層io中。
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}
}
結論:
OutputStream的快取器(陣列)與BufferedOutputStream中類的快取器(陣列)本質是一樣的,只是BufferedOutputStream類中將要寫入到底層io流中的資料先湊個整,然後再一起寫入底層io流中,這樣就大大節省了io操作,大大提高了io利用率,寫一次io是很費資源的。這樣也出現了一個問題,假設向硬碟中寫入一個檔案,檔案最後資料比預設值8192個位元組小,則BufferOutputStream就不會將這些資料寫入底層io流中,造成檔案缺失,因此就需要在close()前呼叫flush()方法,強制將還沒有裝滿buf陣列的資料寫入底層io中。同時也可以看出節點流是不用flush()方法的,而一般的處理流都會採用固定buf這種方式的,比如常用的PrintWriter裡面其實操作的就是一個BufferedWriter物件,因此也需要呼叫flush()方法來重新整理,因為預設是不重新整理的。