2011-05-07 19 views
123

Tengo un InputStream que paso a un método para hacer algún 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 clonar un InputStream?

¿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"; 
    } 
} 
+0

¿Desea "restablecer" la transmisión después de que el método haya regresado? Es decir, ¿leer la transmisión desde el principio? – aioobe

+0

Sí, los métodos que cierran el InputStream devuelven el juego de caracteres que estaba codificado. El segundo método es convertir el InputStream a una Cadena utilizando el juego de caracteres encontrado en el primer método. –

+0

En ese caso, debe poder hacer lo que estoy describiendo en mi respuesta. – Kaj

Respuesta

144

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

A continuación, puede obtener la matriz asociada de bytes y abrir tantos "clonados" ByteArrayInputStream 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 se necesita para mantener el flujo original abierta para recibir nuevos datos, a continuación, tendrá que realizar un seguimiento de este método externo close() y evitar que sea llamado de alguna manera.

+0

Tengo otra solución a mi problema que no implica copiar el InputStream, pero creo que si necesito copiar el InputStream, esta es la mejor solución . –

+0

Ese código hace exactamente lo que describí en mi respuesta – Kaj

+5

Este enfoque consume memoria proporcional al contenido completo de la corriente de entrada. Es mejor usar 'TeeInputStream' como se describe en la respuesta en [aquí] (http://stackoverflow.com/questions/12107049/how-can-i-make-a-copy-of-a-bufferedreader). – aioobe

9

No se puede clonar, y la forma en que se va a resolver el problema depende de la fuente de los datos.

Una solución es leer todos los datos del InputStream en un conjunto de bytes, y luego crear un ByteArrayInputStream alrededor de ese conjunto de bytes, y pasar ese flujo 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.

+0

¿Puede mostrarme algún código? –

+0

No sé en qué parte necesita ayuda.¿Supongo que sabes leer de una transmisión? Lee todos los datos de InputStream y escribe los datos en ByteArrayOutputStream. Llame aByteArray() en ByteArrayOutputStream después de haber completado la lectura de todos los datos. Luego pase esa matriz de bytes al constructor de un ByteArrayInputStream. – Kaj

20

que desea utilizar Apache de CloseShieldInputStream:

Este es un envoltorio que impida el flujo de cerrarse. 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(); 
+0

Se ve bien, pero no funciona aquí. Editaré mi publicación con el código. –

+0

'CloseShield' no funciona porque su flujo de entrada' HttpURLConnection' original está siendo cerrado en alguna parte. ¿No debería su método llamar a IOUtils con la secuencia protegida 'IOUtils.toString (csContent, charset)'? –

+0

Quizás puede ser esto. Puedo evitar que la HttpURLConnection se cierre? –

6

Si los datos leídos de la secuencia 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.

4

Esto podría no funcionar en todas las situaciones, pero esto es lo que hice: amplié la clase FilterInputStream y realicé el procesamiento requerido de los bytes cuando 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 
    } 

} 

Entonces sólo tiene que pasar una instancia de StreamBytesWithExtraProcessingInputStream donde habría pasado en el flujo 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.

+0

Solución elegante. – n13

-1

La siguiente clase 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 secuencias 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(); 
       } 
      } 
     } 
    } 
} 
+0

No responde la pregunta. Quiere usar la secuencia en un método para determinar el juego de caracteres y * luego * volver a leerlo junto con su juego de caracteres en un segundo método. – EJP

3

Si está utilizando apache.commons puede copiar corrientes usando IOUtils.

Puede utilizar 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

<dependency> 
    <groupId>commons-io</groupId> 
    <artifactId>commons-io</artifactId> 
    <version>2.4</version> 
</dependency> 

Gradle

'commons-io:commons-io:2.4' 

Aquí es la referencia DOC para este método:

Obtiene totalidad del contenido de un InputStream y representan mismos datos que 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)

+1

Su enlace está muerto ... –

+0

@ByteCommander Gracias! ¡Fijo! –

+1

Esto no * clona * la secuencia de entrada, pero solo la almacena en búfer. Eso no es lo mismo; el OP quiere volver a leer (una copia de) el mismo flujo. – Raphael

0

La clonación de un flujo de entrada podría no ser una buena idea, ya que esto requiere un conocimiento profundo sobre los detalles de la corriente de entrada que se clona. Una solución para esto es crear una nueva corriente de entrada que lea de la misma fuente nuevamente.

Así, utilizando algunos de Java 8 Características Este sería el siguiente:

public class Foo { 

    private Supplier<InputStream> 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 que va a 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 en 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 cadena localmente primero y proporcionará un InputStream para ese recurso ahora local.

+0

Esta respuesta no está clara para mí. ¿Cómo se inicializa el proveedor de un 'es' existente? – user1156544

+0

@ user1156544 Como escribí * Clonar una secuencia de entrada puede no ser una buena idea, porque esto requiere un conocimiento profundo de los detalles de la secuencia de entrada que se está clonando. * No se puede usar el proveedor para crear una secuencia de entrada de una existente . El proveedor podría usar un 'java.io.File' o' java.net.URL', por ejemplo, para crear una nueva corriente de entrada cada vez que se invoca. – SpaceTrucker

+0

Ahora veo. Esto no funcionará con inputstream como lo pide OP explícitamente, sino con File o URL si son la fuente de datos original. Gracias – user1156544

Cuestiones relacionadas