2011-05-07 113 views
123

我有一個InputStream傳遞給一個方法來做一些處理。我將在其他方法中使用相同的InputStream,但在第一次處理後,InputStream出現在方法內部關閉。如何克隆InputStream?

如何克隆InputStream發送給關閉他的方法?還有另一個解決方案?

編輯:關閉InputStream的方法是從lib的外部方法。我沒有控制關閉與否。

private String getContent(HttpURLConnection con) { 
    InputStream content = null; 
    String charset = ""; 
    try { 
     content = con.getInputStream(); 
     CloseShieldInputStream csContent = new CloseShieldInputStream(content); 
     charset = getCharset(csContent);    
     return IOUtils.toString(content,charset); 
    } catch (Exception e) { 
     System.out.println("Error downloading page: " + e); 
     return null; 
    } 
} 

private String getCharset(InputStream content) { 
    try { 
     Source parser = new Source(content); 
     return parser.getEncoding(); 
    } catch (Exception e) { 
     System.out.println("Error determining charset: " + e); 
     return "UTF-8"; 
    } 
} 
+0

你要「重置」的流方法返回之後?也就是說,從一開始就讀流? – aioobe 2011-05-07 20:43:51

+0

是的,關閉的InputStream方法返回它編碼的字符集。第二種方法是使用第一個方法中的字符集將InputStream轉換爲字符串。 – 2011-05-07 20:49:14

+0

你應該在這種情況下能夠做我在我的回答中描述的內容。 – Kaj 2011-05-07 20:55:43

回答

144

如果你想要做的就是多次讀取相同的信息,並輸入數據是足夠小,以適應到內存中,你可以從你的InputStream將數據複製到一個ByteArrayOutputStream

然後你就可以得到字節數組相關,併爲你喜歡打開多個「克隆」 ByteArrayInputStream秒。

ByteArrayOutputStream baos = new ByteArrayOutputStream(); 

// Fake code simulating the copy 
// You can generally do better with nio if you need... 
// And please, unlike me, do something about the Exceptions :D 
byte[] buffer = new byte[1024]; 
int len; 
while ((len = input.read(buffer)) > -1) { 
    baos.write(buffer, 0, len); 
} 
baos.flush(); 

// Open new InputStreams using the recorded bytes 
// Can be repeated as many times as you wish 
InputStream is1 = new ByteArrayInputStream(baos.toByteArray()); 
InputStream is2 = new ByteArrayInputStream(baos.toByteArray()); 

但是,如果你真的需要保留原始流開來接收新的數據,則需要跟蹤這個外部close()方法,並防止它被稱爲莫名其妙。

+0

我從另一個解決方案,我沒有涉及到複製InputStream的問題,但我認爲如果我需要複製InputStream,這是最好的解決方案。 – 2011-05-08 05:03:29

+0

該代碼與我在回答中描述的完全相同 – Kaj 2011-05-08 07:49:31

+5

此方法消耗的內存與輸入流的全部內容成比例。更好地使用'TeeInputStream'在[這裏]的答案中描述(http://stackoverflow.com/questions/12107049/how-can-i-make-a-copy-of-a-bufferedreader)。 – aioobe 2013-03-03 10:13:49

9

你不能克隆它,你將如何解決你的問題取決於什麼數據的來源。

一種解決方案是從InputStream的所有數據讀入一個字節數組,然後圍繞該字節數組一個ByteArrayInputStream,並傳遞輸入流中的方法。

編輯1: 即,如果其它方法也需要讀取相同的數據。即,您想要「重置」流。

+0

可以給我看一些代碼嗎? – 2011-05-07 20:57:21

+0

我不知道你需要什麼幫助。我想你知道如何從流中讀取?讀取InputStream中的所有數據,並將數據寫入ByteArrayOutputStream。讀完所有數據後,在ByteArrayOutputStream上調用toByteArray()。然後將該字節數組傳遞給ByteArrayInputStream的構造函數。 – Kaj 2011-05-07 21:07:22

20

你要使用Apache的CloseShieldInputStream

這是一個包裝,以防止流被關閉。你會做這樣的事情。

InputStream is = null; 

is = getStream(); //obtain the stream 
CloseShieldInputStream csis = new CloseShieldInputStream(is); 

// call the bad function that does things it shouldn't 
badFunction(csis); 

// happiness follows: do something with the original input stream 
is.read(); 
+0

看起來不錯,但不在這裏工作。我將用代碼編輯我的帖子。 – 2011-05-07 21:23:50

+0

'CloseShield'不工作,因爲您的原始'HttpURLConnection'輸入流beeing關閉某處。你的方法不應該用保護流'IOUtils.toString(csContent,charset)'調用IOUtils? – 2011-05-07 21:41:25

+0

也許可以是這個。我可以防止從HttpURLConnection被關閉? – 2011-05-07 22:16:28

6

如果從流中讀取的數據是大的,我會建議使用Apache的共享IO一個TeeInputStream。這樣你可以基本上覆制輸入,並將你的克隆傳遞給t'd管道。

4

這可能並非在所有情況下工作,但這裏是我做過什麼:我延長了FilterInputStream類和做字節需要處理的外部lib中讀取數據。

public class StreamBytesWithExtraProcessingInputStream extends FilterInputStream { 

    protected StreamBytesWithExtraProcessingInputStream(InputStream in) { 
     super(in); 
    } 

    @Override 
    public int read() throws IOException { 
     int readByte = super.read(); 
     processByte(readByte); 
     return readByte; 
    } 

    @Override 
    public int read(byte[] buffer, int offset, int count) throws IOException { 
     int readBytes = super.read(buffer, offset, count); 
     processBytes(buffer, offset, readBytes); 
     return readBytes; 
    } 

    private void processBytes(byte[] buffer, int offset, int readBytes) { 
     for (int i = 0; i < readBytes; i++) { 
      processByte(buffer[i + offset]); 
     } 
    } 

    private void processByte(int readByte) { 
     // TODO do processing here 
    } 

} 

然後,您只需傳遞StreamBytesWithExtraProcessingInputStream您將在輸入流中傳遞的實例。以原始輸入流作爲構造器參數。

應當注意的是,這個工程逐字節,所以如果高性能是要求不要用這個。

+0

優雅的解決方案。 – n13 2014-05-23 05:10:48

-1

下面的類應該做的伎倆。只需創建一個實例,調用「multiply」方法,並提供源輸入流和所需的重複數量。

重要:你必須同時消耗所有克隆流在單獨的線程。

package foo.bar; 

import java.io.IOException; 
import java.io.InputStream; 
import java.io.PipedInputStream; 
import java.io.PipedOutputStream; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 

public class InputStreamMultiplier { 
    protected static final int BUFFER_SIZE = 1024; 
    private ExecutorService executorService = Executors.newCachedThreadPool(); 

    public InputStream[] multiply(final InputStream source, int count) throws IOException { 
     PipedInputStream[] ins = new PipedInputStream[count]; 
     final PipedOutputStream[] outs = new PipedOutputStream[count]; 

     for (int i = 0; i < count; i++) 
     { 
      ins[i] = new PipedInputStream(); 
      outs[i] = new PipedOutputStream(ins[i]); 
     } 

     executorService.execute(new Runnable() { 
      public void run() { 
       try { 
        copy(source, outs); 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } 
      } 
     }); 

     return ins; 
    } 

    protected void copy(final InputStream source, final PipedOutputStream[] outs) throws IOException { 
     byte[] buffer = new byte[BUFFER_SIZE]; 
     int n = 0; 
     try { 
      while (-1 != (n = source.read(buffer))) { 
       //write each chunk to all output streams 
       for (PipedOutputStream out : outs) { 
        out.write(buffer, 0, n); 
       } 
      } 
     } finally { 
      //close all output streams 
      for (PipedOutputStream out : outs) { 
       try { 
        out.close(); 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } 
      } 
     } 
    } 
} 
+0

不回答問題。他希望在一種方法中使用該流來確定字符集,然後*再用另一種方法重新讀取它的字符集。 – EJP 2014-10-01 07:31:54

3

如果您正在使用apache.commons您可以使用IOUtils複製流。

您可以使用下面的代碼:

InputStream = IOUtils.toBufferedInputStream(toCopy); 

這裏是適合自己情況的完整的例子:

public void cloneStream() throws IOException{ 
    InputStream toCopy=IOUtils.toInputStream("aaa"); 
    InputStream dest= null; 
    dest=IOUtils.toBufferedInputStream(toCopy); 
    toCopy.close(); 
    String result = new String(IOUtils.toByteArray(dest)); 
    System.out.println(result); 
} 

此代碼需要一些依賴關係:

MAVEN

<dependency> 
    <groupId>commons-io</groupId> 
    <artifactId>commons-io</artifactId> 
    <version>2.4</version> 
</dependency> 

搖籃

'commons-io:commons-io:2.4' 

下面是該方法的DOC參考:

獲取一個InputStream的全部內容和表示相同的數據,結果 的InputStream。這種方法是有用的,

來源的InputStream是緩慢的。它有關聯的網絡資源,所以我們 不能把它打開很長一段時間。它具有關聯的網絡超時。

您可以在這裏找到更多關於IOUtilshttp://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStream(java.io.InputStream)

+1

你的鏈接已經死了... – 2016-11-14 09:18:07

+0

@ByteCommander謝謝!固定! – 2016-11-25 17:26:27

+1

這不會*克隆*輸入流,但只緩衝它。這不一樣; OP想要重新讀取(複製)相同的流。 – Raphael 2017-01-23 14:31:48

0

克隆的輸入流可能不是一個好主意,因爲這需要對輸入流的細節深入的瞭解被克隆。解決方法是創建一個新的輸入流,再次從同一個源讀取。

因此,使用一些Java 8層的功能,這將是這樣的:

public class Foo { 

    private Supplier<InputStream> inputStreamSupplier; 

    public void bar() { 
     procesDataThisWay(inputStreamSupplier.get()); 
     procesDataTheOtherWay(inputStreamSupplier.get()); 
    } 

    private void procesDataThisWay(InputStream) { 
     // ... 
    } 

    private void procesDataTheOtherWay(InputStream) { 
     // ... 
    } 
} 

這種方法具有積極的作用,它會重用代碼已經到位 - 創建封裝在inputStreamSupplier輸入流。並且不需要維護用於克隆流的第二代碼路徑。另一方面,如果從流中讀取代價昂貴(因爲它是通過低帶寬連接完成的),那麼這種方法將使成本增加一倍。這可以通過使用將首先在本地存儲數據流內容,併爲現在本地資源的InputStream特定的供應商規避。

+0

這個答案對我而言並不清楚。你如何從現有的「is」初始化供應商? – user1156544 2017-04-10 13:41:23

+0

@ user1156544正如我寫的*克隆輸入流可能不是一個好主意,因爲這需要深入瞭解被克隆的輸入流的詳細信息。*不能使用供應商在現有輸入流之前創建輸入流。例如,供應商可以使用'java.io.File'或'java.net.URL'來在每次調用時創建一個新的輸入流。 – SpaceTrucker 2017-04-10 16:22:48

+0

我現在明白了。這不會與OP明確要求的inputstream一起使用,但是如果它們是原始數據源,則使用File或URL。謝謝 – user1156544 2017-04-12 10:52:48