Pregunta

Tengo un InputStream de un archivo y lo uso poi componentes Apache para leer de esta manera:

POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);

El problema es que i necesidad de utilizar el mismo flujo de múltiples veces y la POIFSFileSystem cierra la corriente después de su uso.

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

EDIT 1:

Por caché i significa el almacén para su uso posterior, no como una forma de speedup la aplicación. También es mejor leer justo arriba de la secuencia de entrada en una matriz o una cadena y luego crear flujos de entrada para cada uso?

EDIT 2:

Lo sentimos reabrir la cuestión, pero las condiciones son algo diferente cuando se trabaja dentro de escritorios y aplicaciones web. En primer lugar, el InputStream que recibo de la org.apache.commons.fileupload.FileItem en mi aplicación Tomcat web no admite marcas por lo tanto no se puede restablecer.

En segundo lugar, me gustaría ser capaz de mantener el archivo en la memoria de acceso más rápidos y menos problemas io cuando se trata de archivos.

¿Fue útil?

Solución

se puede decorar InputStream que se pasa a POIFSFileSystem con una versión que cuando close () se llama que 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_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);
}

editar 2

se puede leer el archivo completo en un [] (modo slurp) de bytes a continuación, pasar a un ByteArrayInputStream

Otros consejos

Trate BufferedInputStream, que añade marca y restablecer la funcionalidad a otro flujo de entrada, y simplemente ignorar sus cerrar:

public class UnclosableBufferedInputStream extends BufferedInputStream {

    public UnclosableBufferedInputStream(InputStream in) {
        super(in);
        super.mark(Integer.MAX_VALUE);
    }

    @Override
    public void close() throws IOException {
        super.reset();
    }
}

Así que:

UnclosableBufferedInputStream  bis = new UnclosableBufferedInputStream (inputStream);

y utilizar siempre que sea bis flujoEntrada se utilizó antes.

Esto funciona correctamente:

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

donde getBytes es la siguiente:

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

La utilización por debajo de implementación para un mayor uso de encargo -

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é quiere usted decir exactamente con "caché"? ¿Quieres que el POIFSFileSystem diferente a empezar por el principio de la corriente? Si es así, no hay absolutamente ningún punto de almacenamiento en caché de cualquier cosa en su código Java; se hará por el sistema operativo, basta con abrir una nueva fuente.

O es que wan para seguir leyendo en el punto en el primer POIFSFileSystem detuvo? Ese no es el almacenamiento en caché, y es muy difícil de hacer. La única manera que se me ocurre si no se puede evitar el flujo de conseguir cerrado sería escribir una envoltura delgada que cuenta cuántos bytes se han leído y luego abrir una nueva ruta y vaya que muchos bytes. Pero eso podría fallar cuando POIFSFileSystem internamente utiliza algo así como un BufferedInputStream.

Si el archivo no es tan grande, leerlo en una matriz de puntos de interés byte[] y darle un ByteArrayInputStream creado a partir de la matriz.

Si el archivo es grande, entonces no se debe cuidar, ya que el sistema operativo hará el almacenamiento en caché para que lo mejor que pueda.

[EDICION] Utilice Apache para leer el archivo en una matriz de bytes en una forma eficiente. No utilice int read() ya que lee el byte a byte de archivos que es muy lento!

Si desea hacerlo usted mismo, utilice un objeto File para obtener la longitud, crear la matriz y el un bucle que lee bytes desde el archivo. Debe bucle desde read(byte[], int offset, int len) puede leer menos de len bytes (y por lo general lo hace).

Así es como me gustaría implementado, para ser utilizado con seguridad con cualquier InputStream:

  • escribir su propio envoltorio InputStream donde se crea un archivo temporal para reflejar el contenido de flujo original
  • volcar todo lo leído en el flujo de entrada original en este archivo temporal
  • cuando la corriente se lee completamente deberá tener todos los datos reflejados en el archivo temporal
  • utilizar InputStream.reset para cambiar (inicializar) la corriente interna a una FileInputStream (mirrored_content_file)
  • a partir de ahora se va a perder la referencia del flujo original (puede ser recogida)
  • añadir un nuevo método de liberación () que eliminará el archivo temporal y liberar cualquier flujo abierto.
  • se puede definir de liberación () de Finalizar para asegurarse que el archivo temporal es la liberación en caso de que se olvide de llamar a la liberación () (la mayoría de las veces se debe evitar el uso de Finalizar , siempre llamar a un método para liberar los recursos del objeto). ve ¿Por qué nunca aplicar finalize ()?
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 forma parte del patrimonio común IO.

esta respuesta itera sobre los anteriores 1 | 2 basado en el BufferInputStream. Los principales cambios son que permite la reutilización infinito. Y se encarga de cerrar el flujo de entrada fuente original para liberar los recursos del sistema. Su sistema operativo define un límite en esos y que no quiere que el programa se ejecute sin identificadores de archivo ( Esa es la razón por la que siempre debe 'consumen' respuestas por ejemplo, con el Apache EntityUtils.consumeQuietly() ). Editar Actualización del código de manejar para los consumidores que utilizan Gready read(buffer, offset, length), en ese caso, puede ocurrir que BufferedInputStream se esfuerza para mirar a la fuente, el 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 volver a utilizar simplemente cerrarlo en primer lugar, si no lo era.

Una limitación es que aunque si se cierra el flujo antes de que todo el contenido de la corriente original ha sido leído, entonces este decorador tendrá datos incompletos, así que asegúrese de toda la corriente se lee antes de cerrar.

Acabo de añadir mi solución a este problema, ya que esto funciona para mí. Básicamente es una combinación de las dos respuestas más:)

    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();
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top