Cómo clonar un InputStream?

Tengo un InputStream que paso a un método para hacer algo de procesamiento. Usaré el mismo InputStream en otro método, pero después del primer procesamiento, el InputStream aparece cerrado dentro del método.

¿Cómo puedo clonar el InputStream para enviarlo al método que lo cierra? Hay otra solución?

EDITAR: los métodos que cierran el InputStream son un método externo de una lib. No tengo control sobre el cierre o no.

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

Si todo lo que quiere hacer es leer la misma información más de una vez, y los datos de entrada son lo suficientemente pequeños como para caber en la memoria, puede copiar los datos de su InputStream a ByteArrayOutputStream .

A continuación, puede obtener la matriz de bytes asociada y abrir tantas ByteArrayInputStream “clonadas” como desee.

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

Pero si realmente necesita mantener la secuencia original abierta para recibir datos nuevos, entonces necesitará seguir este método externo close() e impedir que se llame de alguna manera.

Desea utilizar CloseShieldInputStream de Apache:

Este es un contenedor que evitará que se cierre la secuencia. Harías algo como esto.

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

No puede clonarlo, y la forma en que resolverá su problema depende de la fuente de los datos.

Una solución es leer todos los datos del InputStream en una matriz de bytes, y luego crear un ByteArrayInputStream alrededor de esa matriz de bytes, y pasar esa stream de entrada a su método.

Edit 1: Es decir, si el otro método también necesita leer los mismos datos. Es decir, quiere “restablecer” la transmisión.

Si los datos leídos de la transmisión son grandes, recomendaría usar un TeeInputStream de Apache Commons IO. De esta forma, puedes replicar esencialmente la entrada y pasar un t’d pipe como tu clon.

Puede que esto no funcione en todas las situaciones, pero esto es lo que hice: amplié la clase FilterInputStream y realicé el procesamiento requerido de los bytes a medida que la lib externa lee los datos.

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

Luego, simplemente pase una instancia de StreamBytesWithExtraProcessingInputStream donde habría pasado en la secuencia de entrada. Con el flujo de entrada original como parámetro de constructor.

Cabe señalar que esto funciona byte para byte, por lo tanto, no lo use si el alto rendimiento es un requisito.

Si está utilizando apache.commons , puede copiar flujos usando IOUtils .

Puede usar el siguiente código:

 InputStream = IOUtils.toBufferedInputStream(toCopy); 

Aquí está el ejemplo completo adecuado para su situación:

 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 requiere algunas dependencias:

MAVEN

  commons-io commons-io 2.4  

GRADLE

 'commons-io:commons-io:2.4' 

Aquí está la referencia DOC para este método:

Recupera todo el contenido de un InputStream y representa los mismos datos que el resultado InputStream. Este método es útil donde,

Source InputStream es lento. Tiene recursos de red asociados, por lo que no podemos mantenerlo abierto durante mucho tiempo. Tiene tiempo de espera de red asociado.

Puede encontrar más información sobre IOUtils aquí: http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStream(java.io.InputStream)

La clonación de una secuencia de entrada puede no ser una buena idea, ya que esto requiere un conocimiento profundo de los detalles de la secuencia de entrada que se está clonando. Una solución para esto es crear una nueva stream de entrada que lea de la misma fuente nuevamente.

Entonces, usando algunas características de Java 8 esto se vería así:

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

Este método tiene el efecto positivo de que reutilizará el código que ya está en su lugar: la creación del flujo de entrada encapsulado en inputStreamSupplier . Y no hay necesidad de mantener una segunda ruta de código para la clonación de la secuencia.

Por otro lado, si la lectura de la secuencia es costosa (porque a se hace a través de una conexión de bajo ancho de banda), este método duplicará los costos. Esto podría evitarse mediante el uso de un proveedor específico que almacenará el contenido de la transmisión en primer lugar localmente y proporcionará un InputStream para ese recurso ahora local.

A continuación está la solución con Kotlin.

Puedes copiar tu InputStream en ByteArray

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

Si necesita leer el byteInputStream varias veces, llame a byteInputStream.reset() antes de leer nuevamente.

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

La clase de abajo debería hacer el truco. Simplemente cree una instancia, llame al método “multiplicar” y proporcione el flujo de entrada de la fuente y la cantidad de duplicados que necesita.

Importante: debe consumir todas las transmisiones clonadas simultáneamente en hilos 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(); } } } } }