接着上篇文章,咱們繼續來學習 Java 中的字節流操做。java
裝飾者流實際上是基於一種設計模式「裝飾者模式」而實現的一種文件 IO 流,而咱們的緩衝流只是其中的一種,咱們一塊兒來看看。git
在這以前,咱們使用的文件讀寫流 FileInputStream 和 FileOutputStream 都是一個字節一個字節的從磁盤讀取或寫入,很是耗時。github
而咱們的緩衝流能夠預先從磁盤一次性讀出指定容量的字節數到內存中,以後的讀取操做將直接從內存中讀取,提升效率。下面咱們一塊兒看看緩衝流的具體實現狀況:設計模式
依然先以 BufferedInputStream 爲例,咱們簡單提一下它的幾個核心屬性:數組
buf 就是用於緩衝讀的字節數組,它的值將隨着流的讀取而不停的被填充,繼然後續的讀操做能夠直接基於這個緩衝數組。bash
DEFAULT_BUFFER_SIZE 規定了默認緩衝區的大小,即 buf 的數組長度。MAX_BUFFER_SIZE 指明瞭緩衝區的上限。微信
count 指向緩衝數組中最後一個有效字節索引後一位。pos 指向下一個待讀取的字節索引位置。學習
markpos 和 marklimit 用於重複讀操做。ui
接着咱們看看 BufferedInputStream 的幾個示例構造器:this
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)
歡迎關注微信公衆號:撲在代碼上的高爾基,全部文章都將同步在公衆號上。