¿Cómo convierto un OutputStream en un InputStream?

12 minutos de lectura

Avatar de usuario de Waypoint
punto de referencia

Estoy en la etapa de desarrollo donde tengo dos módulos y de uno obtuve salida como OutputStreamy una segunda que acepta sólo InputStream.

¿Sabes cómo convertir OutputStream a InputStream (no al revés, quiero decir realmente de esta manera) para poder conectar estas dos partes?

  • Ver stackoverflow.com/questions/1225909/…

    – Bala R.

    25 de abril de 2011 a las 13:14

  • @c0mrade, el operador quiere algo como IOUtils.copy, solo que en la otra dirección. Cuando alguien escribe en un OutputStream, queda disponible para que otra persona lo use en un InputStream. Esto es básicamente lo que hace PipedOutputStream/PipedInputStream. Lamentablemente, los flujos canalizados no se pueden crear a partir de otros flujos.

    – MeBigFatGuy

    25 de abril de 2011 a las 13:17


  • Entonces, ¿PipedOutputStream/PipedInputStream es la solución?

    – Punto de referencia

    25 de abril de 2011 a las 13:19

  • Básicamente, para que PipedStreams funcione en su caso, su OutputStream debería construirse como new YourOutputStream(thePipedOutputStream) y new YourInputStream(thePipedInputStream) que probablemente no sea la forma en que funciona su transmisión. Así que no creo que esta sea la solución.

    – MeBigFatGuy

    25 de abril de 2011 a las 13:40

avatar de usuario de mikeho
mikeho

Parece que hay muchos enlaces y otras cosas similares, pero no hay código real que use tuberías. La ventaja de usar java.io.PipedInputStream y java.io.PipedOutputStream es que no hay consumo adicional de memoria. ByteArrayOutputStream.toByteArray() devuelve una copia del búfer original, lo que significa que lo que sea que tenga en la memoria, ahora tiene dos copias. Luego, escribiendo a un InputStream significa que ahora tiene tres copias de los datos.

El código usando lambdas (punta de sombrero a @John Manko de los comentarios):

PipedInputStream in = new PipedInputStream();
final PipedOutputStream out = new PipedOutputStream(in);
// in a background thread, write the given output stream to the
// PipedOutputStream for consumption
new Thread(() -> {originalOutputStream.writeTo(out);}).start();

Una cosa que notó @John Manko es que en ciertos casos, cuando no tienes el control de la creación del Flujo de salidapuede terminar en una situación en la que el creador puede limpiar el Flujo de salida objeto de forma prematura. Si está obteniendo el ClosedPipeExceptionentonces deberías intentar invertir los constructores:

PipedInputStream in = new PipedInputStream(out);
new Thread(() -> {originalOutputStream.writeTo(out);}).start();

Tenga en cuenta que también puede invertir los constructores para los ejemplos a continuación.

Gracias también a @AlexK por corregirme al comenzar un Thread en lugar de simplemente iniciar una Runnable.


El código usando try-with-resources:

// take the copy of the stream and re-write it to an InputStream
PipedInputStream in = new PipedInputStream();
    new Thread(new Runnable() {
        public void run () {
            // try-with-resources here
            // putting the try block outside the Thread will cause the
            // PipedOutputStream resource to close before the Runnable finishes
            try (final PipedOutputStream out = new PipedOutputStream(in)) {
                // write the original OutputStream to the PipedOutputStream
                // note that in order for the below method to work, you need
                // to ensure that the data has finished writing to the
                // ByteArrayOutputStream
                originalByteArrayOutputStream.writeTo(out);
            }
            catch (IOException e) {
                // logging and exception handling should go here
            }
        }
    }).start();

El código original que escribí:

// take the copy of the stream and re-write it to an InputStream
PipedInputStream in = new PipedInputStream();
final PipedOutputStream out = new PipedOutputStream(in);
new Thread(new Runnable() {
    public void run () {
        try {
            // write the original OutputStream to the PipedOutputStream
            // note that in order for the below method to work, you need
            // to ensure that the data has finished writing to the
            // ByteArrayOutputStream
            originalByteArrayOutputStream.writeTo(out);
        }
        catch (IOException e) {
            // logging and exception handling should go here
        }
        finally {
            // close the PipedOutputStream here because we're done writing data
            // once this thread has completed its run
            if (out != null) {
                // close the PipedOutputStream cleanly
                out.close();
            }
        }   
    }
}).start();

Este código asume que el originalByteArrayOutputStream es un ByteArrayOutputStream ya que suele ser el único flujo de salida utilizable, a menos que esté escribiendo en un archivo. Lo mejor de esto es que, dado que está en un hilo separado, también funciona en paralelo, por lo que lo que sea que esté consumiendo su flujo de entrada también se transmitirá desde su antiguo flujo de salida. Eso es beneficioso porque el búfer puede permanecer más pequeño y tendrá menos latencia y menos uso de memoria.

Si no tienes un ByteArrayOutputStreamentonces en lugar de usar writeTo()tendrás que usar uno de los write() métodos en el java.io.OutputStream clase o uno de los otros métodos disponibles en una subclase.

  • Voté esto a favor, pero es mejor pasar out a inconstructor de; de ​​lo contrario, podría obtener una excepción de tubería cerrada en in debido a la condición de carrera (que experimenté). Usando Java 8 Lambdas: PipedInputStream in = new PipedInputStream(out); ((Runnable)() -> {originalOutputStream.writeTo(out);}).run(); return in;

    – John Manko

    13 de septiembre de 2015 a las 5:07


  • No, mi caso surge cuando almaceno archivos PDF en Mongo GridFS y luego transmito al cliente usando Jax-RS. MongoDB proporciona un OutputStream, pero Jax-RS requiere un InputStream. Parece que mi método de ruta regresaría al contenedor con un InputStream antes de que el OutputStream se estableciera por completo (tal vez el búfer aún no se había almacenado en caché). De todos modos, Jax-RS arrojaría una excepción de tubería cerrada en InputStream. Extraño, pero eso es lo que sucedió la mitad del tiempo. Cambiar al código anterior evita eso.

    – John Manko

    14/09/2015 a las 21:12


  • @JohnManko Estaba investigando esto más y vi desde el PipedInputStream Javadocs: Se dice que una canalización está rota si un subproceso que proporcionaba bytes de datos al flujo de salida canalizado conectado ya no está activo. Entonces, lo que sospecho es que si está usando el ejemplo anterior, el hilo se está completando antes Jax-RS está consumiendo el flujo de entrada. Al mismo tiempo, miré a la MongoDB Javadocs. GridFSDBFile tiene un flujo de entrada, así que ¿por qué no simplemente pasar eso a Jax-RS?

    – mikeho

    15/09/2015 a las 16:44

  • @DennisCheung sí, por supuesto. Nada es gratis, pero ciertamente será más pequeño que una copia de 15 MB. Las optimizaciones incluirían el uso de un grupo de subprocesos en su lugar para reducir la rotación de GC con la creación constante de subprocesos/objetos.

    – mikeho

    27 de julio de 2016 a las 18:53

  • Tenga en cuenta que PipedInputStream y PipedOutputStream deben estar ambos en un subproceso separado; de lo contrario, se puede producir un interbloqueo después de cierto tamaño (consulte el documento de Java: docs.oracle.com/javase/7/docs/api/java/io/PipedInputStream.html)

    – Elektropepi

    27 de agosto de 2020 a las 12:47


Avatar de usuario de Java Drinker
Bebedor de Java

Un OutputStream es uno donde escribes datos. Si algún módulo expone un OutputStreamla expectativa es que haya algo leyendo en el otro extremo.

Algo que expone un InputStreampor otro lado, indica que necesitará escuchar esta transmisión y habrá datos que podrá leer.

Entonces es posible conectar un InputStream a una OutputStream

InputStream----read---> intermediateBytes[n] ----write----> OutputStream

Como alguien mencionó, esto es lo que el copy() método de IOUtils te permite hacer No tiene sentido ir por el otro lado… ojalá esto tenga algún sentido

ACTUALIZAR:

Por supuesto, cuanto más pienso en esto, más puedo ver cómo esto realmente sería un requisito. Conozco algunos de los comentarios mencionados. Piped flujos de entrada/salida, pero hay otra posibilidad.

Si el flujo de salida que está expuesto es un ByteArrayOutputStreamsiempre puede obtener el contenido completo llamando al toByteArray() método. Luego puede crear un contenedor de flujo de entrada usando el ByteArrayInputStream subclase. Estos dos son pseudo flujos, ambos básicamente envuelven una matriz de bytes. Usar los flujos de esta manera, por lo tanto, es técnicamente posible, pero para mí sigue siendo muy extraño…

  • copy () haga esto IS para el sistema operativo de acuerdo con la API, necesito que lo haga al revés

    – Punto de referencia

    25 de abril de 2011 a las 13:47

  • El caso de uso es muy simple: imagine que tiene una biblioteca de serialización (por ejemplo, serializar a JSON) y una capa de transporte (digamos, Tomcat) que toma un InputStream. Por lo tanto, debe canalizar OutputStream desde JSON a través de una conexión HTTP que quiere leer desde InputStream.

    – JBCP

    21 de febrero de 2014 a las 17:28

  • Esto es útil cuando se realizan pruebas unitarias y se es muy pedante a la hora de evitar tocar el sistema de archivos.

    – Jon

    13 de marzo de 2014 a las 9:26

  • El comentario de @JBCP es acertado. Otro caso de uso es usar PDFBox para crear archivos PDF durante una solicitud HTTP. PDFBox usando un OutputStream para guardar un objeto PDF, y la API REST acepta un InputStream para responder al cliente. Por lo tanto, un OutputStream -> InputStream es un caso de uso muy real.

    – John Manko

    12/09/2015 a las 18:47

  • “siempre puede obtener el contenido completo llamando al método toByteArray()” ¡el objetivo de usar flujos es no cargar todo el contenido en la memoria!

    – pila desbordada

    8 de abril de 2020 a las 7:06

Avatar de usuario de BorutT
borut

Como los flujos de entrada y salida son solo un punto de inicio y final, la solución es almacenar datos temporalmente en una matriz de bytes. Por lo que debe crear intermedio ByteArrayOutputStreama partir del cual creas byte[] que se utiliza como entrada para nuevos ByteArrayInputStream.

public void doTwoThingsWithStream(InputStream inStream, OutputStream outStream){ 
  //create temporary bayte array output stream
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  doFirstThing(inStream, baos);
  //create input stream from baos
  InputStream isFromFirstData = new ByteArrayInputStream(baos.toByteArray()); 
  doSecondThing(isFromFirstData, outStream);
}

Espero eso ayude.

ByteArrayOutputStream buffer = (ByteArrayOutputStream) aOutputStream;
byte[] bytes = buffer.toByteArray();
InputStream inputStream = new ByteArrayInputStream(bytes);

avatar de usuario de mckamey
mckamey

Necesitará una clase intermedia que actuará como búfer. Cada vez InputStream.read(byte[]...) se llama, la clase de almacenamiento en búfer llenará la matriz de bytes pasada con el siguiente fragmento pasado desde OutputStream.write(byte[]...). Dado que los tamaños de los fragmentos pueden no ser los mismos, la clase de adaptador deberá almacenar una cierta cantidad hasta que tenga suficiente para llenar el búfer de lectura y/o poder almacenar cualquier desbordamiento del búfer.

Este artículo tiene un buen desglose de algunos enfoques diferentes para este problema:

http://blog.ostermiller.org/convert-java-outputstream-inputstream

  • gracias @mckamey, ¡el método basado en Circular Buffers es exactamente lo que necesito!

    –Hui Wang

    07/04/2016 a las 14:25

El corriente fácil La biblioteca de código abierto tiene soporte directo para convertir un OutputStream en un InputStream: http://io-tools.sourceforge.net/easystream/tutorial/tutorial.html

// create conversion
final OutputStreamToInputStream<Void> out = new OutputStreamToInputStream<Void>() {
    @Override
    protected Void doRead(final InputStream in) throws Exception {
           LibraryClass2.processDataFromInputStream(in);
           return null;
        }
    };
try {   
     LibraryClass1.writeDataToTheOutputStream(out);
} finally {
     // don't miss the close (or a thread would not terminate correctly).
     out.close();
}

También enumeran otras opciones: http://io-tools.sourceforge.net/easystream/outputstream_to_inputstream/implementations.html

  • Escriba los datos en un búfer de memoria (ByteArrayOutputStream), obtenga el byteArray y léalo nuevamente con un ByteArrayInputStream. Este es el mejor enfoque si está seguro de que sus datos caben en la memoria.
  • Copie sus datos en un archivo temporal y vuelva a leerlos.
  • Use pipes: este es el mejor enfoque tanto para el uso de la memoria como para la velocidad (puede aprovechar al máximo los procesadores multinúcleo) y también la solución estándar que ofrece Sun.
  • Utilice InputStreamFromOutputStream y OutputStreamToInputStream de la biblioteca easystream.

  • gracias @mckamey, ¡el método basado en Circular Buffers es exactamente lo que necesito!

    –Hui Wang

    07/04/2016 a las 14:25

Encontré el mismo problema al convertir un ByteArrayOutputStream a un ByteArrayInputStream y lo resolvió usando una clase derivada de ByteArrayOutputStream que es capaz de devolver un ByteArrayInputStream que se inicializa con el búfer interno del ByteArrayOutputStream. De esta forma no se usa memoria adicional y la ‘conversión’ es muy rápida:

package info.whitebyte.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

/**
 * This class extends the ByteArrayOutputStream by 
 * providing a method that returns a new ByteArrayInputStream
 * which uses the internal byte array buffer. This buffer
 * is not copied, so no additional memory is used. After
 * creating the ByteArrayInputStream the instance of the
 * ByteArrayInOutStream can not be used anymore.
 * <p>
 * The ByteArrayInputStream can be retrieved using <code>getInputStream()</code>.
 * @author Nick Russler
 */
public class ByteArrayInOutStream extends ByteArrayOutputStream {
    /**
     * Creates a new ByteArrayInOutStream. The buffer capacity is
     * initially 32 bytes, though its size increases if necessary.
     */
    public ByteArrayInOutStream() {
        super();
    }

    /**
     * Creates a new ByteArrayInOutStream, with a buffer capacity of
     * the specified size, in bytes.
     *
     * @param   size   the initial size.
     * @exception  IllegalArgumentException if size is negative.
     */
    public ByteArrayInOutStream(int size) {
        super(size);
    }

    /**
     * Creates a new ByteArrayInputStream that uses the internal byte array buffer 
     * of this ByteArrayInOutStream instance as its buffer array. The initial value 
     * of pos is set to zero and the initial value of count is the number of bytes 
     * that can be read from the byte array. The buffer array is not copied. This 
     * instance of ByteArrayInOutStream can not be used anymore after calling this
     * method.
     * @return the ByteArrayInputStream instance
     */
    public ByteArrayInputStream getInputStream() {
        // create new ByteArrayInputStream that respects the current count
        ByteArrayInputStream in = new ByteArrayInputStream(this.buf, 0, this.count);

        // set the buffer of the ByteArrayOutputStream 
        // to null so it can't be altered anymore
        this.buf = null;

        return in;
    }
}

Puse las cosas en github: https://github.com/nickrussler/ByteArrayInOutStream

  • ¿Qué sucede si el contenido no cabe en el búfer?

    – Vad1mo

    15 de septiembre de 2014 a las 9:20

  • Entonces, en primer lugar, no debe usar un ByteArrayInputStream.

    – Nick Russler

    15 de septiembre de 2014 a las 9:39

  • Esta solución tendrá todos los bytes en la memoria. Para archivos pequeños, esto estará bien, pero también puede usar getBytes () en ByteArrayOutput Stream

    – Vad1mo

    15/09/2014 a las 11:40

  • Si te refieres a matriz de bytes esto haría que se copiara el búfer interno, lo que requeriría el doble de memoria que mi enfoque. Editar: Ah, entiendo, para archivos pequeños esto funciona, por supuesto …

    – Nick Russler

    15/09/2014 a las 15:40


  • Pérdida de tiempo. ByteArrayOutputStream tiene un método writeTo para transferir contenido a otro flujo de salida

    – Tony BenBrahim

    17 de febrero de 2015 a las 18:38

¿Ha sido útil esta solución?