BufferedOutputStream的快取功能解析(原始碼閱讀)

小鞅發表於2016-05-09

要介紹BufferedOutputStream,我們先了解一下OutputStream類
抽象類OutputStream類有三個write方法

  1. public abstract void write(int b)
  2. public void write(byte b[])
  3. 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()方法來重新整理,因為預設是不重新整理的。

參考:
http://www.bdqn.cn/news/201311/12111.shtml

<script type="text/javascript"> $(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('<ul/>').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($('<li/>').text(i)); }; $numbering.fadeIn(1700); }); }); </script>

相關文章