2011-05-07 235 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