Cómo guardar en caché InputStream para uso múltiple

Tengo un InputStream de un archivo y uso componentes de apache poi para leer de este modo:

POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream); 

El problema es que necesito usar el mismo flujo varias veces y POIFSFileSystem cierra el flujo después de su uso.

¿Cuál es la mejor manera de almacenar en caché los datos de la secuencia de entrada y luego servir más flujos de entrada a diferentes POIFSFileSystem?

EDIT 1:

Por caché quiero decir almacenar para un uso posterior, no como una forma de acelerar la aplicación. ¿También es mejor simplemente leer el flujo de entrada en una matriz o cadena y luego crear flujos de entrada para cada uso?

EDICION 2:

Perdón por volver a abrir la pregunta, pero las condiciones son algo diferentes cuando se trabaja en el escritorio y la aplicación web. En primer lugar, el InputStream que obtengo de org.apache.commons.fileupload.FileItem en mi aplicación web de tomcat no admite marcas, por lo que no se puede restablecer.

En segundo lugar, me gustaría poder mantener el archivo en la memoria para un acceso más rápido y menos problemas al tratar con archivos.

puede decorar InputStream pasando a POIFSFileSystem con una versión que cuando se llama close () responde con reset ():

 class ResetOnCloseInputStream extends InputStream { private final InputStream decorated; public ResetOnCloseInputStream(InputStream anInputStream) { if (!anInputStream.markSupported()) { throw new IllegalArgumentException("marking not supported"); } anInputStream.mark( 1 << 24); // magic constant: BEWARE decorated = anInputStream; } @Override public void close() throws IOException { decorated.reset(); } @Override public int read() throws IOException { return decorated.read(); } } 

caso de prueba

 static void closeAfterInputStreamIsConsumed(InputStream is) throws IOException { int r; while ((r = is.read()) != -1) { System.out.println(r); } is.close(); System.out.println("========="); } public static void main(String[] args) throws IOException { InputStream is = new ByteArrayInputStream("sample".getBytes()); ResetOnCloseInputStream decoratedIs = new ResetOnCloseInputStream(is); closeAfterInputStreamIsConsumed(decoratedIs); closeAfterInputStreamIsConsumed(decoratedIs); closeAfterInputStreamIsConsumed(is); } 

EDIT 2

puede leer todo el archivo en un byte [] (modo sorber) y luego pasarlo a ByteArrayInputStream

Pruebe BufferedInputStream, que agrega la funcionalidad de marca y restablecimiento a otra secuencia de entrada, y simplemente anula su método de cierre:

 public class UnclosableBufferedInputStream extends BufferedInputStream { public UnclosableBufferedInputStream(InputStream in) { super(in); super.mark(Integer.MAX_VALUE); } @Override public void close() throws IOException { super.reset(); } } 

Asi que:

 UnclosableBufferedInputStream bis = new UnclosableBufferedInputStream (inputStream); 

y use bis dondequiera que se usó inputStream antes.

Esto funciona correctamente:

 byte[] bytes = getBytes(inputStream); POIFSFileSystem fileSystem = new POIFSFileSystem(new ByteArrayInputStream(bytes)); 

donde getBytes es así:

 private static byte[] getBytes(InputStream is) throws IOException { byte[] buffer = new byte[8192]; ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); int n; baos.reset(); while ((n = is.read(buffer, 0, buffer.length)) != -1) { baos.write(buffer, 0, n); } return baos.toByteArray(); } 

Use la implementación a continuación para un uso más personalizado:

 public class ReusableBufferedInputStream extends BufferedInputStream { private int totalUse; private int used; public ReusableBufferedInputStream(InputStream in, Integer totalUse) { super(in); if (totalUse > 1) { super.mark(Integer.MAX_VALUE); this.totalUse = totalUse; this.used = 1; } else { this.totalUse = 1; this.used = 1; } } @Override public void close() throws IOException { if (used < totalUse) { super.reset(); ++used; } else { super.close(); } } } 

¿Qué quieres decir exactamente con “caché”? ¿Desea que los diferentes POIFSFileSystem comiencen al principio de la transmisión? Si es así, no tiene sentido almacenar nada en el código de Java; lo hará el SO, solo abra una nueva transmisión.

¿O desea continuar leyendo en el punto donde se detuvo el primer POIFSFileSystem? Eso no es almacenamiento en caché, y es muy difícil de hacer. La única forma en que puedo pensar si no puede evitar que la transmisión se cierre sería escribir un contenedor fino que cuente cuántos bytes se han leído y luego abrir una nueva transmisión y omitir esos muchos bytes. Pero eso podría fallar cuando POIFSFileSystem usa internamente algo como un BufferedInputStream.

Si el archivo no es tan grande, léelo en una matriz de byte[] y dale a POI un ByteArrayInputStream creado a partir de esa matriz.

Si el archivo es grande, entonces no debería importarle, ya que el sistema operativo hará el almacenamiento en caché para usted lo mejor que pueda.

[EDIT] Usa Apache commons-io para leer el archivo en una matriz de bytes de una manera eficiente. No use int read() ya que lee el byte del archivo byte, que es muy lento.

Si desea hacerlo usted mismo, use un objeto File para obtener la longitud, cree la matriz y el bucle a que lee los bytes del archivo. Debe realizar un bucle desde la read(byte[], int offset, int len) puede leer menos de len bytes (y normalmente lo hace).

Así es como lo implementaría, para ser utilizado con seguridad con cualquier InputStream:

  • escribe tu propia envoltura de InputStream donde creas un archivo temporal para reflejar el contenido de la secuencia original
  • volcar todo lo leído desde la secuencia de entrada original en este archivo temporal
  • cuando la secuencia se haya leído por completo, tendrá todos los datos reflejados en el archivo temporal
  • use InputStream.reset para cambiar (inicializar) la transmisión interna a FileInputStream (mirrored_content_file)
  • de ahora en adelante perderá la referencia de la transmisión original (se puede recostackr)
  • agregue una nueva versión de método () que eliminará el archivo temporal y liberará cualquier secuencia abierta.
  • incluso puede llamar a release () desde finalize para asegurarse de que se libere el archivo temporal en caso de que olvide llamar a release () (la mayoría de las veces debe evitar el uso de finalize , siempre llame a un método para liberar recursos de objeto). ver ¿Por qué implementarías alguna vez finalizar ()?
 public static void main(String[] args) throws IOException { BufferedInputStream inputStream = new BufferedInputStream(IOUtils.toInputStream("Foobar")); inputStream.mark(Integer.MAX_VALUE); System.out.println(IOUtils.toString(inputStream)); inputStream.reset(); System.out.println(IOUtils.toString(inputStream)); } 

Esto funciona. IOUtils es parte de IO de los comunes.

Esta respuesta itera sobre las anteriores 1 | 2 basado en BufferInputStream . Los principales cambios son que permite la reutilización infinita. Y se encarga de cerrar el flujo de entrada de la fuente original para liberar recursos del sistema. Su sistema operativo define un límite para ellos y no desea que el progtwig se quede sin identificadores de archivo ( Es por eso que siempre debe ‘consumir’ las respuestas, por ejemplo, con Apache EntityUtils.consumeQuietly() ). EDITAR Actualizó el código a manejar para los consumidores gready que usan read(buffer, offset, length) , en ese caso puede suceder que BufferedInputStream intente buscar la fuente, este código protege contra ese uso.

 public class CachingInputStream extends BufferedInputStream { public CachingInputStream(InputStream source) { super(new PostCloseProtection(source)); super.mark(Integer.MAX_VALUE); } @Override public synchronized void close() throws IOException { if (!((PostCloseProtection) in).decoratedClosed) { in.close(); } super.reset(); } private static class PostCloseProtection extends InputStream { private volatile boolean decoratedClosed = false; private final InputStream source; public PostCloseProtection(InputStream source) { this.source = source; } @Override public int read() throws IOException { return decoratedClosed ? -1 : source.read(); } @Override public int read(byte[] b) throws IOException { return decoratedClosed ? -1 : source.read(b); } @Override public int read(byte[] b, int off, int len) throws IOException { return decoratedClosed ? -1 : source.read(b, off, len); } @Override public long skip(long n) throws IOException { return decoratedClosed ? 0 : source.skip(n); } @Override public int available() throws IOException { return source.available(); } @Override public void close() throws IOException { decoratedClosed = true; source.close(); } @Override public void mark(int readLimit) { source.mark(readLimit); } @Override public void reset() throws IOException { source.reset(); } @Override public boolean markSupported() { return source.markSupported(); } } } 

Para reutilizarlo, ciérrelo primero si no fuera así.

Sin embargo, una limitación es que si la transmisión se cierra antes de que se haya leído todo el contenido de la transmisión original, este decorador tendrá datos incompletos, por lo que debe asegurarse de leer toda la secuencia antes de cerrar.

Solo agrego mi solución aquí, ya que esto funciona para mí. Básicamente es una combinación de las dos mejores respuestas 🙂

  private String convertStreamToString(InputStream is) { Writer w = new StringWriter(); char[] buf = new char[1024]; Reader r; is.mark(1 << 24); try { r = new BufferedReader(new InputStreamReader(is, "UTF-8")); int n; while ((n=r.read(buf)) != -1) { w.write(buf, 0, n); } is.reset(); } catch(UnsupportedEncodingException e) { Logger.debug(this.getClass(), "Cannot convert stream to string.", e); } catch(IOException e) { Logger.debug(this.getClass(), "Cannot convert stream to string.", e); } return w.toString(); }