InputStream中通過mark和reset方法重復利用緩存
阿新 • • 發佈:2018-01-21
輸出 string sys 都是 實現 源碼 常用 ges 不支持
其實InputStream本身提供了三個接口:
第一個,InputStream是否支持mark,默認不支持。
Java代碼
第二個,mark接口。該接口在InputStream中默認實現不做任何事情。
Java代碼
第三個,reset接口。該接口在InputStream中實現,調用就會拋異常。
Java代碼
從三個接口定義中可以看出,首先InputStream默認是不支持mark的,子類需要支持mark必須重寫這三個方法。
第一個接口很簡單,就是標明該InputStream是否支持mark。
調用mark方法會記下當前調用mark方法的時刻,InputStream被讀到的位置。
調用reset方法就會回到該位置。
舉個簡單的例子:
Java代碼
看了這個例子之後對mark和reset接口有了很直觀的認識。
mark接口的參數readlimit作用
我們知道InputStream是不支持mark的。要想支持mark子類必須重寫這三個方法,我想說的是不同的實現子類,mark的參數readlimit作用不盡相同。
常用的FileInputStream不支持mark。
1. 對於BufferedInputStream,readlimit表示:InputStream調用mark方法的時刻起,在讀取readlimit個字節之前,標記的該位置是有效的。如果讀取的字節數大於readlimit,可能標記的位置會失效。
在BufferedInputStream的read方法源碼中有這麽一段:
Java代碼
為什麽是可能會失效呢?
因為BufferedInputStream讀取不是一個字節一個字節讀取的,是一個字節數組一個字節數組讀取的。
例如,readlimit=35,第一次比較的時候buffer.length=0(沒開始讀)<readlimit
然後buffer數組一次讀取48個字節。這時的read方法只會簡單的挨個返回buffer數組中的字節,不會做這次比較。直到讀到buffer數組最後一個字節(第48個)後,才重新再次比較。這時如果我們讀到buffer中第47個字節就reset。mark仍然是有效的。雖然47>35。
2. 對於InputStream的另外一個實現類:ByteArrayInputStream,我們發現readlimit參數根本就沒有用,調用mark方法的時候寫多少都無所謂。
Java代碼
因為對於ByteArrayInputStream來說,都是通過字節數組創建的,內部本身就保存了整個字節數組,mark只是標記一下數組下標位置,根本不用擔心mark會創建太大的buffer字節數組緩存。
3. 其他的InputStream子類沒有去總結。原理都是一樣的。
所以由於mark和reset方法配合可以記錄並回到我們標記的流的位置重新讀流,很大一部分就可以解決我們的某些重復讀的需要。
這種方式的優點很明顯:不用緩存整個InputStream數據。對於ByteArrayInputStream甚至沒有任何的內存開銷。
當然這種方式也有缺點:就是需要通過幹擾InputStream的讀取細節,也相對比較復雜。
通過緩存InputStream可重復利用一個InputStream,但是要緩存一整個InputStream內存壓力可能是比較大的。如果第一次讀取InputStream是用來判斷文件流類型,文件編碼等用的,往往不需要所有的InputStream的數據,或許只需要前n個字節,這樣一來,緩存一整個InputStream實際上也是一種浪費。
其實InputStream本身提供了三個接口:
第一個,InputStream是否支持mark,默認不支持。
Java代碼
- public boolean markSupported() {
- return false;
- }
第二個,mark接口。該接口在InputStream中默認實現不做任何事情。
Java代碼
- public synchronized void mark(int readlimit) {}
第三個,reset接口。該接口在InputStream中實現,調用就會拋異常。
Java代碼
- public synchronized void reset() throws IOException {
- throw new IOException("mark/reset not supported");
- }
從三個接口定義中可以看出,首先InputStream默認是不支持mark的,子類需要支持mark必須重寫這三個方法。
第一個接口很簡單,就是標明該InputStream是否支持mark。
調用mark方法會記下當前調用mark方法的時刻,InputStream被讀到的位置。
調用reset方法就會回到該位置。
舉個簡單的例子:
Java代碼
- String content = "BoyceZhang!";
- InputStream inputStream = new ByteArrayInputStream(content.getBytes());
- // 判斷該輸入流是否支持mark操作
- if (!inputStream.markSupported()) {
- System.out.println("mark/reset not supported!");
- }
- int ch;
- boolean marked = false;
- while ((ch = inputStream.read()) != -1) {
- //讀取一個字符輸出一個字符
- System.out.print((char)ch);
- //讀到 ‘e‘的時候標記一下
- if (((char)ch == ‘e‘)& !marked) {
- inputStream.mark(content.length()); //先不要理會mark的參數
- marked = true;
- }
- //讀到‘!‘的時候重新回到標記位置開始讀
- if ((char)ch == ‘!‘ && marked) {
- inputStream.reset();
- marked = false;
- }
- }
- //程序最終輸出:BoyceZhang!Zhang!
看了這個例子之後對mark和reset接口有了很直觀的認識。
mark接口的參數readlimit作用
我們知道InputStream是不支持mark的。要想支持mark子類必須重寫這三個方法,我想說的是不同的實現子類,mark的參數readlimit作用不盡相同。
常用的FileInputStream不支持mark。
1. 對於BufferedInputStream,readlimit表示:InputStream調用mark方法的時刻起,在讀取readlimit個字節之前,標記的該位置是有效的。如果讀取的字節數大於readlimit,可能標記的位置會失效。
在BufferedInputStream的read方法源碼中有這麽一段:
Java代碼
- } else if (buffer.length >= marklimit) {
- markpos = -1; /* buffer got too big, invalidate mark */
- pos = 0; /* drop buffer contents */
- } else { /* grow buffer */
為什麽是可能會失效呢?
因為BufferedInputStream讀取不是一個字節一個字節讀取的,是一個字節數組一個字節數組讀取的。
例如,readlimit=35,第一次比較的時候buffer.length=0(沒開始讀)<readlimit
然後buffer數組一次讀取48個字節。這時的read方法只會簡單的挨個返回buffer數組中的字節,不會做這次比較。直到讀到buffer數組最後一個字節(第48個)後,才重新再次比較。這時如果我們讀到buffer中第47個字節就reset。mark仍然是有效的。雖然47>35。
2. 對於InputStream的另外一個實現類:ByteArrayInputStream,我們發現readlimit參數根本就沒有用,調用mark方法的時候寫多少都無所謂。
Java代碼
- public void mark(int readAheadLimit) {
- mark = pos;
- }
- public synchronized void reset() {
- pos = mark;
- }
因為對於ByteArrayInputStream來說,都是通過字節數組創建的,內部本身就保存了整個字節數組,mark只是標記一下數組下標位置,根本不用擔心mark會創建太大的buffer字節數組緩存。
3. 其他的InputStream子類沒有去總結。原理都是一樣的。
所以由於mark和reset方法配合可以記錄並回到我們標記的流的位置重新讀流,很大一部分就可以解決我們的某些重復讀的需要。
這種方式的優點很明顯:不用緩存整個InputStream數據。對於ByteArrayInputStream甚至沒有任何的內存開銷。
當然這種方式也有缺點:就是需要通過幹擾InputStream的讀取細節,也相對比較復雜。
InputStream中通過mark和reset方法重復利用緩存