如何克隆 InputStream?

我有一个 InputStream,我传递给一个方法来进行一些处理。我将在其他方法中使用相同的 InputStream,但是在第一次处理之后,InputStream 在方法中显示为关闭的。

我如何克隆 InputStream 发送到关闭它的方法? 还有其他解决方案吗?

编辑: 关闭 InputStream 的方法是来自库的外部方法。我无法控制关闭与否。

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";
}
}
178712 次浏览

您不能克隆它,并且您将如何解决您的问题取决于数据的来源是什么。

一种解决方案是将 InputStream 中的所有数据读入一个字节数组,然后围绕该字节数组创建一个 ByteArrayInputStream,并将该输入流传递给您的方法。

< p > 编辑1: 也就是说,如果其他方法也需要读取相同的数据,也就是说,您想要“重置”流

你想使用 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();

如果您只想多次读取相同的信息,并且输入数据足够小以容纳到内存中,那么您可以将数据从 InputStream复制到 字节数组输出流

然后,您可以获得相关的字节数组,并打开任意数量的“克隆”字节数组输入流

ByteArrayOutputStream baos = new ByteArrayOutputStream();


// Code simulating the copy
// You could alternatively use NIO
// 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 recorded bytes
// Can be repeated as many times as you wish
InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
InputStream is2 = new ByteArrayInputStream(baos.toByteArray());

但是,如果您确实需要保持原始流打开以接收新数据,那么您将需要跟踪到 close()的外部调用。您将需要防止 close()以某种方式被调用。

更新(2019) :

因为 Java9的中间位可以替换为 InputStream.transferTo:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
input.transferTo(baos);
InputStream firstClone = new ByteArrayInputStream(baos.toByteArray());
InputStream secondClone = new ByteArrayInputStream(baos.toByteArray());

如果从流中读取的数据很大,我建议使用 ApacheCommons IO 中的 TeeInputStream。通过这种方式,您实际上可以复制输入,并将 t’d 管道作为您的克隆传递。

这可能不适用于所有情况,但是我这样做了: 我扩展了 过滤输入流类,并在外部库读取数据时执行所需的字节处理。

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实例,在该实例中您将传递输入流。以原始输入流作为构造函数参数。

应该注意的是,这种方法对字节有效,所以如果需要高性能,就不要使用这种方法。

下面的类应该可以解决这个问题。只需创建一个实例,调用“乘法”方法,并提供源输入流和所需的重复数量。

重要提示: 必须在单独的线程中同时使用所有克隆的流。

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();
}
}
}
}
}
< p > UPD. 检查之前的评论。它不完全是被问到的

如果您正在使用 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 速度很慢,它有相关的网络资源,所以我们 无法长时间保持打开状态。它与网络超时有关
你可以在这里找到更多关于 IOUtils的资料: http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStream(java.io.InputStream)

克隆输入流可能不是一个好主意,因为这需要对被克隆的输入流的细节有深入的了解。解决这个问题的方法是创建一个新的输入流,它再次从相同的源读取数据。

使用 Java8的一些特性,它看起来像这样:

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

下面是 Kotlin 的解决方案。

您可以将 InputStream 复制到 ByteArray 中

val inputStream = ...


val byteOutputStream = ByteArrayOutputStream()
inputStream.use { input ->
byteOutputStream.use { output ->
input.copyTo(output)
}
}


val byteInputStream = ByteArrayInputStream(byteOutputStream.toByteArray())

如果需要多次读取 byteInputStream,请在再次读取之前调用 byteInputStream.reset()

https://code.luasoftware.com/tutorials/kotlin/how-to-clone-inputstream/

用示例增强 @Anthony Accioly

InputStream: 克隆 bytes-Stream并提供作为 List 集合的副本数。

public static List<InputStream> multiplyBytes(InputStream input, int cloneCount) throws IOException {
List<InputStream> copies = new ArrayList<InputStream>();
    

ByteArrayOutputStream baos = new ByteArrayOutputStream();
copy(input, baos);
    

for (int i = 0; i < cloneCount; i++) {
copies.add(new ByteArrayInputStream(baos.toByteArray()));
}
return copies;
}
// IOException - If reading the Reader or Writing into the Writer goes wrong.
public static void copy(Reader in, Writer out) throws IOException {
try {
char[] buffer = new char[1024];
int nrOfBytes = -1;
while ((nrOfBytes = in.read(buffer)) != -1) {
out.write(buffer, 0, nrOfBytes);
}
out.flush();
} finally {
close(in);
close(out);
}
}

读者: 克隆 chars-Stream并提供作为 List 集合的副本数。

public static List<Reader> multiplyChars(Reader reader, int cloneCOunt) throws IOException {
List<Reader> copies = new ArrayList<Reader>();
BufferedReader bufferedInput = new BufferedReader(reader);
StringBuffer buffer = new StringBuffer();
String delimiter = System.getProperty("line.separator");
String line;
while ((line = bufferedInput.readLine()) != null) {
if (!buffer.toString().equals(""))
buffer.append(delimiter);
buffer.append(line);
}
close(bufferedInput);
for (int i = 0; i < cloneCOunt; i++) {
copies.add(new StringReader(buffer.toString()));
}
return copies;
}
public static void copy(InputStream in, OutputStream out) throws IOException {
try {
byte[] buffer = new byte[1024];
int nrOfBytes = -1;
while ((nrOfBytes = in.read(buffer)) != -1) {
out.write(buffer, 0, nrOfBytes);
}
out.flush();
} finally {
close(in);
close(out);
}
}

完整例子:

public class SampleTest {


public static void main(String[] args) throws IOException {
String filePath = "C:/Yash/StackoverflowSSL.cer";
InputStream fileStream = new FileInputStream(new File(filePath) );
        

List<InputStream> bytesCopy = multiplyBytes(fileStream, 3);
for (Iterator<InputStream> iterator = bytesCopy.iterator(); iterator.hasNext();) {
InputStream inputStream = (InputStream) iterator.next();
System.out.println("Byte Stream:"+ inputStream.available()); // Byte Stream:1784
}
printInputStream(bytesCopy.get(0));
        

//java.sql.Clob clob = ((Clob) getValue(sql)); - clob.getCharacterStream();
Reader stringReader = new StringReader("StringReader that reads Characters from the specified string.");
List<Reader> charsCopy = multiplyChars(stringReader, 3);
for (Iterator<Reader> iterator = charsCopy.iterator(); iterator.hasNext();) {
Reader reader = (Reader) iterator.next();
System.out.println("Chars Stream:"+reader.read()); // Chars Stream:83
}
printReader(charsCopy.get(0));
}
    

// Reader, InputStream - Prints the contents of the reader to System.out.
public static void printReader(Reader reader) throws IOException {
BufferedReader br = new BufferedReader(reader);
String s;
while ((s = br.readLine()) != null) {
System.out.println(s);
}
}
public static void printInputStream(InputStream inputStream) throws IOException {
printReader(new InputStreamReader(inputStream));
}


// Closes an opened resource, catching any exceptions.
public static void close(Closeable resource) {
if (resource != null) {
try {
resource.close();
} catch (IOException e) {
System.err.println(e);
}
}
}
}