punto de referencia
Estoy en la etapa de desarrollo donde tengo dos módulos y de uno obtuve salida como OutputStream
y 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?
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 ClosedPipeException
entonces 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 ByteArrayOutputStream
entonces 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
ain
constructor de; de lo contrario, podría obtener una excepción de tubería cerrada enin
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 antesJax-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
Bebedor de Java
Un OutputStream
es uno donde escribes datos. Si algún módulo expone un OutputStream
la expectativa es que haya algo leyendo en el otro extremo.
Algo que expone un InputStream
por 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 ByteArrayOutputStream
siempre 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
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 ByteArrayOutputStream
a 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.
-
baos.toByteArray() crea una copia con System.arraycopy. Gracias a @mikeho por señalar desarrollador.classpath.org/doc/java/io/…
– Mitja Gustin
5 dic 2018 a las 20:30
ByteArrayOutputStream buffer = (ByteArrayOutputStream) aOutputStream;
byte[] bytes = buffer.toByteArray();
InputStream inputStream = new ByteArrayInputStream(bytes);
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
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)
ynew 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