如何克隆InputStream?
我有一个InputStream传递给一个方法做一些处理。 我将在其他方法中使用相同的InputStream,但在第一次处理后,InputStream出现在方法内部。
如何克隆InputStream发送给closures他的方法? 还有另一个解决scheme?
编辑:closuresInputStream的方法是从lib的外部方法。 我没有控制closures或没有。
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"; } }
如果您只想多次读取相同的信息,并且input的数据足够小以适应内存,则可以将数据从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());
但是如果你真的需要保持原始stream打开来接收新的数据,那么你将需要跟踪这个外部close()
方法,并防止它以某种方式被调用。
你想使用Apache的CloseShieldInputStream
:
这是一个封装,将阻止stream被closures。 你会做这样的事情。
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();
你无法克隆它,你将如何解决你的问题取决于数据的来源。
一种解决方法是将InputStream中的所有数据读取到一个字节数组中,然后在该字节数组周围创build一个ByteArrayInputStream,并将该inputstream传递到您的方法中。
编辑1:也就是说,如果其他方法也需要读取相同的数据。 即你想“重置”stream。
如果从数据stream中读取的数据很大,我build议使用Apache Commons IO的TeeInputStream。 这样,你可以基本上复制input,并传递一个t'dpipe道作为你的克隆。
这可能不适用于所有情况,但这是我做的:我扩展了FilterInputStream类,并在外部库读取数据时执行所需的字节处理。
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
的实例,您将在inputstream中传递该实例。 以原始inputstream作为构造器参数。
应该注意的是,这个字节是针对字节的,所以如果需要高性能的话就不要使用这个字节。
如果您使用apache.commons
,则可以使用IOUtils
复制stream。
你可以使用下面的代码:
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很慢。 它有相关的networking资源,所以我们不能长时间保持开放。 它有networking超时关联。
你可以在这里find关于IOUtils
更多信息: http : IOUtils
)
克隆inputstream可能不是一个好主意,因为这需要深入了解被克隆的inputstream的细节。 解决方法是创build一个新的inputstream,再次从相同的源读取。
所以使用一些Java 8的function,看起来像这样:
public class Foo { private Supplier<InputStream> inputStreamSupplier; public void bar() { procesDataThisWay(inputStreamSupplier.get()); procesDataTheOtherWay(inputStreamSupplier.get()); } private void procesDataThisWay(InputStream) { // ... } private void procesDataTheOtherWay(InputStream) { // ... } }
这个方法具有积极的作用,它将重用已经存在的代码 – 创build在inputStreamSupplier
封装的inputstream。 并且不需要维护用于克隆stream的第二代码path。
另一方面,如果从stream中读取是昂贵的(因为它是通过低带宽连接完成的),那么这种方法将使成本加倍。 这可以通过使用特定的供应商来绕开,该供应商将首先在本地存储stream内容,并为当前的本地资源提供InputStream
。
下面的类应该做的伎俩。 只要创build一个实例,调用“multiply”方法,并提供源inputstream和你需要的重复数量。
重要说明:您必须在不同的线程中同时使用所有克隆的stream。
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(); } } } } }