Cómo almacenar en caché InputStream para el Uso Múltiple
-
06-09-2019 - |
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.
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();
}