接著上篇文章,我們繼續(xù)來學(xué)習(xí) java 中的字節(jié)流操作。
裝飾者緩沖流 bufferedinput/outputstream
裝飾者流其實(shí)是基于一種設(shè)計(jì)模式「裝飾者模式」而實(shí)現(xiàn)的一種文件 io 流,而我們的緩沖流只是其中的一種,我們一起來看看。
在這之前,我們使用的文件讀寫流 fileinputstream 和 fileoutputstream 都是一個(gè)字節(jié)一個(gè)字節(jié)的從磁盤讀取或?qū)懭耄浅:臅r(shí)。
而我們的緩沖流可以預(yù)先從磁盤一次性讀出指定容量的字節(jié)數(shù)到內(nèi)存中,之后的讀取操作將直接從內(nèi)存中讀取,提高效率。下面我們一起看看緩沖流的具體實(shí)現(xiàn)情況:
依然先以 bufferedinputstream 為例,我們簡(jiǎn)單提一下它的幾個(gè)核心屬性:
- private static int default_buffer_size = 8192;
- protected volatile byte buf[];
- private static int max_buffer_size = integer.max_value - 8;
- protected int count;
- protected int pos;
- protected int markpos = -1;
- protected int marklimit;
buf 就是用于緩沖讀的字節(jié)數(shù)組,它的值將隨著流的讀取而不停的被填充,繼而后續(xù)的讀操作可以直接基于這個(gè)緩沖數(shù)組。
default_buffer_size 規(guī)定了默認(rèn)緩沖區(qū)的大小,即 buf 的數(shù)組長(zhǎng)度。max_buffer_size 指明了緩沖區(qū)的上限。
count 指向緩沖數(shù)組中最后一個(gè)有效字節(jié)索引后一位。pos 指向下一個(gè)待讀取的字節(jié)索引位置。
markpos 和 marklimit 用于重復(fù)讀操作。
接著我們看看 bufferedinputstream 的幾個(gè)示例構(gòu)造器:
1
2
3
|
public bufferedinputstream(inputstream in) { this (in, default_buffer_size); } |
1
2
3
4
5
6
7
|
public bufferedinputstream(inputstream in, int size) { super (in); if (size <= 0 ) { throw new illegalargumentexception( "buffer size <= 0" ); } buf = new byte [size]; } |
整體上來說,前者只需要傳入一個(gè)「被裝飾」的 inputstream 實(shí)例,并使用默認(rèn)大小的緩沖區(qū)。后者則可以顯式指明緩沖區(qū)的大小。
除此之外,super(in) 會(huì)將這個(gè) inputstream 實(shí)例保存進(jìn)父類 filterinputstream 的 in 屬性字段中,并且所有實(shí)際的磁盤讀操作都由這個(gè) inputstream 實(shí)例發(fā)出。
下面我們來看最重要的讀操作以及緩沖區(qū)是如何被填充的。
1
2
3
4
5
6
7
8
|
public synchronized int read() throws ioexception { if (pos >= count) { fill(); if (pos >= count) return - 1 ; } return getbufifopen()[pos++] & 0xff ; } |
這個(gè)方法想必大家已經(jīng)很熟悉了,從流中讀取下一個(gè)字節(jié)并返回,但細(xì)節(jié)上的實(shí)現(xiàn)還是稍稍有些不同。
count 指向了緩沖數(shù)組中有效字節(jié)索引后一位置處,pos 指向下一個(gè)待讀取的字節(jié)索引位置。理論上 pos 是不可能大于 count 的,最多等于。
如果 pos 等于 count,那說明緩沖數(shù)組中所有有效字節(jié)都已經(jīng)被讀取過了,此時(shí)即需要丟棄緩沖區(qū)中那些「無用」的數(shù)據(jù),從磁盤重新加載一批新數(shù)據(jù)填充緩沖區(qū)。
而事實(shí)上,fill 方法就是做的這個(gè)事情,它的代碼比較多,就不帶大家去解析了,你理解了它的作用,想必分析它的實(shí)現(xiàn)也是容易的。
如果 fill 方法調(diào)用之后,pos 依然 等于 count,那么說明 inputstream 實(shí)例并沒有從流中讀取出任何數(shù)據(jù),也即文件流中無數(shù)據(jù)可讀。關(guān)于這一點(diǎn),參見 fill 方法 246 行。
總的來說,如果成功填充了緩沖區(qū),那么我們的 read 方法將直接從緩沖區(qū)取出一個(gè)字節(jié)返回給調(diào)用者。
1
2
3
|
public synchronized int read( byte b[], int off, int len){ //..... } |
這個(gè)方法也是「熟人」了,不再多余的解釋了,實(shí)現(xiàn)是類似的。
skip 方法用于跳過指定長(zhǎng)度的字節(jié)數(shù)進(jìn)行文件流的繼續(xù)讀取:
1
2
3
|
public synchronized long skip( long n){ //..... } |
注意一點(diǎn)的是,skip 方法盡量去跳過 n 個(gè)字節(jié),但不保證一定跳過 n 個(gè)字節(jié),方法返回的是實(shí)際跳過的字節(jié)數(shù)。如果緩沖數(shù)組中剩余可用字節(jié)數(shù)小于 n,那么最終將跳過緩沖數(shù)組中實(shí)際可跳過的字節(jié)數(shù)。
最后要說一說這個(gè) close 方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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 方法將賦空「被裝飾者」流,并調(diào)用它的 close 方法釋放相關(guān)資源,最終也會(huì)清空緩沖數(shù)組所占用的內(nèi)存空間。
bufferedinputstream 提供了讀緩沖能力,而 bufferedoutputstream 則提供了寫緩沖能力,即內(nèi)存的寫操作并不會(huì)立馬更新到磁盤,暫時(shí)保存在緩沖區(qū),待緩沖區(qū)滿時(shí)一并寫入。
1
2
3
|
protected byte buf[]; protected int count; |
buf 代表了內(nèi)部緩沖區(qū),count 表示緩沖區(qū)中實(shí)際數(shù)據(jù)容量,即 buf 中有效字節(jié)數(shù),而不是 buf 數(shù)組長(zhǎng)度。
1
2
3
4
5
6
7
8
9
10
11
|
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]; } |
一樣的實(shí)現(xiàn)思路,必須提供的是一個(gè) outputstream 輸出流實(shí)例,也可以選擇性指明緩沖區(qū)大小。
1
2
3
4
5
6
|
public synchronized void write( int b) throws ioexception { if (count >= buf.length) { flushbuffer(); } buf[count++] = ( byte )b; } |
寫方法將首先檢查緩沖區(qū)是否還能容納本次寫操作,如果不能將發(fā)起一次磁盤寫操作,將緩沖區(qū)數(shù)據(jù)全部寫入磁盤文件,否則將優(yōu)先寫入緩沖區(qū)。
當(dāng)然,bufferedoutputstream 也提供了 flush 方法向外提供接口,也即不一定非要等到緩沖區(qū)滿了才向磁盤寫數(shù)據(jù),你也可以顯式的調(diào)用該方法讓它清空緩沖區(qū)并更新磁盤文件。
1
2
3
4
|
public synchronized void flush() throws ioexception { flushbuffer(); out.flush(); } |
關(guān)于緩沖流,核心內(nèi)容介紹如上,這是一種能夠顯著提升效率的流,通過它,能夠減少磁盤訪問次數(shù),提升程序執(zhí)行效率。
有關(guān)對(duì)象序列化流 objectinput/outputstream 以及基于基本類型的裝飾者流 datainput/outputstream 我們這里暫時(shí)不做討論。待到我們學(xué)習(xí)序列化的時(shí)候,再回頭討論這兩個(gè)字節(jié)流。
文章中的所有代碼、圖片、文件都云存儲(chǔ)在我的 github 上:
(https://github.com/singleyam/overview_java)
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)服務(wù)器之家的支持。
原文鏈接:https://www.cnblogs.com/yangming1996/p/9046337.html