1. 程式人生 > >InputStream中通過mark和reset方法重復利用緩存

InputStream中通過mark和reset方法重復利用緩存

輸出 string sys 都是 實現 源碼 常用 ges 不支持

通過緩存InputStream可重復利用一個InputStream,但是要緩存一整個InputStream內存壓力可能是比較大的。如果第一次讀取InputStream是用來判斷文件流類型,文件編碼等用的,往往不需要所有的InputStream的數據,或許只需要前n個字節,這樣一來,緩存一整個InputStream實際上也是一種浪費。

其實InputStream本身提供了三個接口:
第一個,InputStream是否支持mark,默認不支持。
Java代碼 技術分享圖片
  1. public boolean markSupported() {
  2. return false;
  3. }

第二個,mark接口。該接口在InputStream中默認實現不做任何事情。
Java代碼 技術分享圖片
  1. public synchronized void mark(int readlimit) {}

第三個,reset接口。該接口在InputStream中實現,調用就會拋異常。
Java代碼 技術分享圖片
  1. public synchronized void reset() throws IOException {
  2. throw new IOException("mark/reset not supported");
  3. }

從三個接口定義中可以看出,首先InputStream默認是不支持mark的,子類需要支持mark必須重寫這三個方法。
第一個接口很簡單,就是標明該InputStream是否支持mark。

調用mark方法會記下當前調用mark方法的時刻,InputStream被讀到的位置。
調用reset方法就會回到該位置。
舉個簡單的例子:
Java代碼 技術分享圖片
  1. String content = "BoyceZhang!";
  2. InputStream inputStream = new ByteArrayInputStream(content.getBytes());
  3. // 判斷該輸入流是否支持mark操作
  4. if (!inputStream.markSupported()) {
  5. System.out.println("mark/reset not supported!");
  6. }
  7. int ch;
  8. boolean marked = false;
  9. while ((ch = inputStream.read()) != -1) {
  10. //讀取一個字符輸出一個字符
  11. System.out.print((char)ch);
  12. //讀到 ‘e‘的時候標記一下
  13. if (((char)ch == ‘e‘)& !marked) {
  14. inputStream.mark(content.length()); //先不要理會mark的參數
  15. marked = true;
  16. }
  17. //讀到‘!‘的時候重新回到標記位置開始讀
  18. if ((char)ch == ‘!‘ && marked) {
  19. inputStream.reset();
  20. marked = false;
  21. }
  22. }
  23. //程序最終輸出:BoyceZhang!Zhang!

看了這個例子之後對mark和reset接口有了很直觀的認識。
mark接口的參數readlimit作用
我們知道InputStream是不支持mark的。要想支持mark子類必須重寫這三個方法,我想說的是不同的實現子類,mark的參數readlimit作用不盡相同。
常用的FileInputStream不支持mark。
1. 對於BufferedInputStream,readlimit表示:InputStream調用mark方法的時刻起,在讀取readlimit個字節之前,標記的該位置是有效的。如果讀取的字節數大於readlimit,可能標記的位置會失效。

在BufferedInputStream的read方法源碼中有這麽一段:
Java代碼 技術分享圖片
  1. } else if (buffer.length >= marklimit) {
  2. markpos = -1; /* buffer got too big, invalidate mark */
  3. pos = 0; /* drop buffer contents */
  4. } 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代碼 技術分享圖片
  1. public void mark(int readAheadLimit) {
  2. mark = pos;
  3. }
  4. public synchronized void reset() {
  5. pos = mark;
  6. }


因為對於ByteArrayInputStream來說,都是通過字節數組創建的,內部本身就保存了整個字節數組,mark只是標記一下數組下標位置,根本不用擔心mark會創建太大的buffer字節數組緩存。

3. 其他的InputStream子類沒有去總結。原理都是一樣的。

所以由於mark和reset方法配合可以記錄並回到我們標記的流的位置重新讀流,很大一部分就可以解決我們的某些重復讀的需要。
這種方式的優點很明顯:不用緩存整個InputStream數據。對於ByteArrayInputStream甚至沒有任何的內存開銷。
當然這種方式也有缺點:就是需要通過幹擾InputStream的讀取細節,也相對比較復雜。

InputStream中通過mark和reset方法重復利用緩存