接著上篇文章,我們繼續來學習Java 中的字節流操作。
裝飾者緩衝流BufferedInput/OutputStream
裝飾者流其實是基於一種設計模式「裝飾者模式」而實現的一種文件IO 流,而我們的緩衝流只是其中的一種,我們一起來看看。
在這之前,我們使用的文件讀寫流FileInputStream 和FileOutputStream 都是一個字節一個字節的從磁盤讀取或寫入,非常耗時。
而我們的緩衝流可以預先從磁盤一次性讀出指定容量的字節數到內存中,之後的讀取操作將直接從內存中讀取,提高效率。下面我們一起看看緩衝流的具體實現情況:
依然先以BufferedInputStream 為例,我們簡單提一下它的幾個核心屬性:
buf 就是用於緩衝讀的字節數組,它的值將隨著流的讀取而不停的被填充,繼而後續的讀操作可以直接基於這個緩衝數組。
DEFAULT_BUFFER_SIZE 規定了默認緩衝區的大小,即buf 的數組長度。 MAX_BUFFER_SIZE 指明了緩衝區的上限。
count 指向緩衝數組中最後一個有效字節索引後一位。 pos 指向下一個待讀取的字節索引位置。
markpos 和marklimit 用於重複讀操作。
接著我們看看BufferedInputStream 的幾個示例構造器:
public BufferedInputStream(InputStream in) { this(in, DEFAULT_BUFFER_SIZE);} public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size];}整體上來說,前者只需要傳入一個「被裝飾」的InputStream 實例,並使用默認大小的緩衝區。後者則可以顯式指明緩衝區的大小。
除此之外,super(in) 會將這個InputStream 實例保存進父類FilterInputStream 的in 屬性字段中,並且所有實際的磁盤讀操作都由這個InputStream 實例發出。
下面我們來看最重要的讀操作以及緩衝區是如何被填充的。
public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff;}這個方法想必大家已經很熟悉了,從流中讀取下一個字節並返回,但細節上的實現還是稍稍有些不同。
count 指向了緩衝數組中有效字節索引後一位置處,pos 指向下一個待讀取的字節索引位置。理論上pos 是不可能大於count 的,最多等於。
如果pos 等於count,那說明緩衝數組中所有有效字節都已經被讀取過了,此時即需要丟棄緩衝區中那些「無用」的數據,從磁盤重新加載一批新數據填充緩衝區。
而事實上,fill 方法就是做的這個事情,它的代碼比較多,就不帶大家去解析了,你理解了它的作用,想必分析它的實現也是容易的。
如果fill 方法調用之後,pos 依然等於count,那麼說明InputStream 實例並沒有從流中讀取出任何數據,也即文件流中無數據可讀。關於這一點,參見fill 方法246 行。
總的來說,如果成功填充了緩衝區,那麼我們的read 方法將直接從緩衝區取出一個字節返回給調用者。
public synchronized int read(byte b[], int off, int len){ //.....}這個方法也是「熟人」了,不再多餘的解釋了,實現是類似的。
skip 方法用於跳過指定長度的字節數進行文件流的繼續讀取:
public synchronized long skip(long n){ //.....}注意一點的是,skip 方法盡量去跳過n 個字節,但不保證一定跳過n 個字節,方法返回的是實際跳過的字節數。如果緩衝數組中剩餘可用字節數小於n,那麼最終將跳過緩衝數組中實際可跳過的字節數。
最後要說一說這個close 方法:
public void close() throws IOException { byte[] buffer; while ( (buffer = buf) != null) { if (bufUpdater.compareAndSet(this, buffer, null)) { InputStream input = in; in = null; if (input != null) input.close(); return; } // Else retry in case a new buf was CASed in fill() }}close 方法將賦空「被裝飾者」流,並調用它的close 方法釋放相關資源,最終也會清空緩衝數組所佔用的內存空間。
BufferedInputStream 提供了讀緩衝能力,而BufferedOutputStream 則提供了寫緩衝能力,即內存的寫操作並不會立馬更新到磁盤,暫時保存在緩衝區,待緩衝區滿時一併寫入。
protected byte buf[];protected int count;
buf 代表了內部緩衝區,count 表示緩衝區中實際數據容量,即buf 中有效字節數,而不是buf 數組長度。
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];}一樣的實現思路,必須提供的是一個OutputStream 輸出流實例,也可以選擇性指明緩衝區大小。
public synchronized void write(int b) throws IOException { if (count >= buf.length) { flushBuffer(); } buf[count++] = (byte)b;}寫方法將首先檢查緩衝區是否還能容納本次寫操作,如果不能將發起一次磁盤寫操作,將緩衝區數據全部寫入磁盤文件,否則將優先寫入緩衝區。
當然,BufferedOutputStream 也提供了flush 方法向外提供接口,也即不一定非要等到緩衝區滿了才向磁盤寫數據,你也可以顯式的調用該方法讓它清空緩衝區並更新磁盤文件。
public synchronized void flush() throws IOException { flushBuffer(); out.flush();}關於緩衝流,核心內容介紹如上,這是一種能夠顯著提升效率的流,通過它,能夠減少磁盤訪問次數,提升程序執行效率。
有關對象序列化流ObjectInput/OutputStream 以及基於基本類型的裝飾者流DataInput/OutputStream 我們這裡暫時不做討論。待到我們學習序列化的時候,再回頭討論這兩個字節流。
文章中的所有代碼、圖片、文件都雲存儲在我的GitHub 上:
(https://github.com/SingleYam/overview_java)
大家也可以選擇通過本地下載。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對武林網的支持。