關於對inputstream流的復制


今天因為項目需要,獲取到一個inputstream后,可能要多次利用它進行read的操作。由於流讀過一次就不能再讀了,所以得想點辦法。

而InputStream對象本身不能復制,因為它沒有實現Cloneable接口。此時,可以先把InputStream轉化成ByteArrayOutputStream,后面要使用InputStream對象時,再從ByteArrayOutputStream轉化回來就好了。代碼實現如下:

 

 

 

  1. InputStream input =  httpconn.getInputStream();  
  2.                   
  3. ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  4. byte[] buffer = new byte[1024];  
  5. int len;  
  6. while ((len = input.read(buffer)) > -1 ) {  
  7.     baos.write(buffer, 0, len);  
  8. }  
  9. baos.flush();                
  10.   
  11. InputStream stream1 = new ByteArrayInputStream(baos.toByteArray());  
  12.   
  13. //TODO:顯示到前台  
  14.   
  15. InputStream stream2 = new ByteArrayInputStream(baos.toByteArray());  
  16.   
  17. //TODO:本地緩存  
InputStream input =  httpconn.getInputStream();
	            
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) > -1 ) {
	baos.write(buffer, 0, len);
}
baos.flush();	           

InputStream stream1 = new ByteArrayInputStream(baos.toByteArray());

//TODO:顯示到前台

InputStream stream2 = new ByteArrayInputStream(baos.toByteArray());

//TODO:本地緩存

這種適用於一些不是很大的流,因為緩存流是會消耗內存的,還有一種方法是用到了流的mark 和 reset方法。
其實InputStream本身提供了三個接口: 第一個,InputStream是否支持mark,默認不支持。
Java代碼 復制代碼  收藏代碼
  1. public boolean markSupported() {  
  2.    return false;  
  3. }  
public boolean markSupported() {
   return false;
}
第二個,mark接口。該接口在InputStream中默認實現不做任何事情。
Java代碼 復制代碼  收藏代碼
  1. public synchronized void mark(int readlimit) {}  
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. }  
public synchronized void reset() throws IOException {
   throw new IOException("mark/reset not supported");
}
從三個接口定義中可以看出,首先InputStream默認是不支持mark的,子類需要支持mark必須重寫這三個方法。 第一個接口很簡單,就是標明該InputStream是否支持mark。 mark接口的官方文檔解釋: “在此輸入流中標記當前的位置。對 reset 方法的后續調用會在最后標記的位置重新定位此流,以便后續讀取重新讀取相同的字節。  readlimit 參數告知此輸入流在標記位置失效之前允許讀取許多字節。  
mark 的常規協定是:如果方法 markSupported 返回 true,則輸入流總會在調用 mark 之后記住所有讀取的字節,並且無論何時調用方法 reset ,都會准備再次提供那些相同的字節。但是,如果在調用 reset 之前可以從流中讀取多於 readlimit 的字節,則根本不需要該流記住任何數據。”

reset接口的官方文檔解釋: 將此流重新定位到對此輸入流最后調用 mark 方法時的位置。  reset 的常規協定是: 
如果方法 markSupported 返回 true,則:  如果創建流以來未調用方法 mark,或最后調用 mark 以來從該流讀取的字節數大於最后調用 mark 時的參數,則可能拋出 IOException。  如果未拋出這樣的 IOException,則將該流重新設置為這種狀態:最近調用 mark 以來(或如果未調用 mark,則從文件開始以來)讀取的所有字節將重新提供給 read 方法的后續調用方,后接可能是調用 reset 時的下一輸入數據的所有字節。  如果方法 markSupported 返回 false,則:  對 reset 的調用可能拋出 IOException。  如果未拋出 IOException,則將該流重新設置為一種固定狀態,該狀態取決於輸入流的特定類型和其創建方式的固定狀態。提供給 read 方法的后續調用方的字節取決於特定類型的輸入流。 

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

public synchronized void reset() {
   pos = mark;
}
因為對於ByteArrayInputStream來說,都是通過字節數組創建的,內部本身就保存了整個字節數組,mark只是標記一下數組下標位置,根本不用擔心mark會創建太大的buffer字節數組緩存。 
3. 其他的InputStream子類沒有去總結。原理都是一樣的。
所以由於mark和reset方法配合可以記錄並回到我們標記的流的位置重新讀流,很大一部分就可以解決我們的某些重復讀的需要。 這種方式的優點很明顯:不用緩存整個InputStream數據。對於ByteArrayInputStream甚至沒有任何的內存開銷。 當然這種方式也有缺點:就是需要通過干擾InputStream的讀取細節,也相對比較復雜。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM