Como clonar um InputStream?

Eu tenho um InputStream que eu passo para um método para fazer algum processamento. Eu vou usar o mesmo InputStream em outro método, mas depois do primeiro processamento, o InputStream aparece fechado dentro do método.

Como eu posso clonar o InputStream para enviar para o método que o fecha? Existe outra solução?

EDIT: os methods que fecham o InputStream são um método externo de um lib. Eu não tenho controle sobre o fechamento ou não.

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"; } } 

Se tudo o que você deseja fazer é ler as mesmas informações mais de uma vez, e os dados de input são pequenos o suficiente para caber na memory, você pode copiar os dados do seu InputStream para um ByteArrayOutputStream .

Em seguida, você pode obter a matriz associada de bytes e abrir quantos ByteArrayInputStream “clonados” desejar.

 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()); 

Mas se você realmente precisa manter o stream original aberto para receber novos dados, então você precisará rastrear este método close() externo e evitar que ele seja chamado de alguma forma.

Você quer usar o CloseShieldInputStream do Apache:

Este é um wrapper que impedirá que o stream seja fechado. Você faria algo assim.

 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(); 

Você não pode cloná-lo, e como você vai resolver seu problema depende de qual é a origem dos dados.

Uma solução é ler todos os dados do InputStream em uma matriz de bytes e, em seguida, criar um ByteArrayInputStream em torno dessa matriz de bytes e transmitir esse stream de input para o método.

Editar 1: Ou seja, se o outro método também precisar ler os mesmos dados. Ou seja, você quer “redefinir” o stream.

Se os dados lidos do stream forem grandes, eu recomendaria usar um TeeInputStream do IO do Apache Commons. Dessa forma, você pode essencialmente replicar a input e passar um tubo t como seu clone.

Isso pode não funcionar em todas as situações, mas eis o que fiz: estendi a class FilterInputStream e fiz o processamento necessário dos bytes à medida que a lib externa lê os dados.

 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 } } 

Então você simplesmente passa uma instância de StreamBytesWithExtraProcessingInputStream onde você teria passado no stream de input. Com o stream de input original como parâmetro construtor.

Deve-se notar que isso funciona byte para byte, portanto, não use isso se o alto desempenho for um requisito.

Se você estiver usando o apache.commons poderá copiar streams usando o IOUtils .

Você pode usar o seguinte código:

 InputStream = IOUtils.toBufferedInputStream(toCopy); 

Aqui está o exemplo completo adequado à sua situação:

 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); } 

Este código requer algumas dependencies:

MAVEN

  commons-io commons-io 2.4  

GRAAL

 'commons-io:commons-io:2.4' 

Aqui está a referência do DOC para este método:

Busca todo o conteúdo de um InputStream e representa os mesmos dados que o resultado do InputStream. Este método é útil onde,

O InputStream de origem é lento. Tem resources de rede associados, por isso não podemos mantê-lo aberto por muito tempo. Tem timeout de rede associado.

Você pode encontrar mais informações sobre o IOUtils aqui: http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStream(java.io.InputStream)

A clonagem de um stream de input pode não ser uma boa ideia, porque isso requer um conhecimento profundo sobre os detalhes do stream de input que está sendo clonado. Uma solução para isso é criar um novo stream de input que leia da mesma fonte novamente.

Então, usando alguns resources do Java 8, ficaria assim:

 public class Foo { private Supplier inputStreamSupplier; public void bar() { procesDataThisWay(inputStreamSupplier.get()); procesDataTheOtherWay(inputStreamSupplier.get()); } private void procesDataThisWay(InputStream) { // ... } private void procesDataTheOtherWay(InputStream) { // ... } } 

Esse método tem o efeito positivo de reutilizar o código que já está em vigor – a criação do stream de input encapsulado em inputStreamSupplier . E não há necessidade de manter um segundo caminho de código para a clonagem do stream.

Por outro lado, se a leitura do stream for cara (porque é feita em uma conexão de baixa largura de banda), esse método duplicará os custos. Isso poderia ser evitado usando um fornecedor específico que armazenará o conteúdo do stream localmente primeiro e fornecerá um InputStream para esse recurso agora local.

Abaixo está a solução com o Kotlin.

Você pode copiar seu InputStream em ByteArray

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

Se você precisar ler o byteInputStream várias vezes, chame byteInputStream.reset() antes de ler novamente.

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

A class abaixo deve fazer o truque. Basta criar uma instância, chamar o método “multiplicar” e fornecer o stream de input de origem e a quantidade de duplicatas de que você precisa.

Importante: você deve consumir todos os streams clonados simultaneamente em encadeamentos separados.

 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(); } } } } }