Come Cache InputStream per un uso multiplo
-
06-09-2019 - |
Domanda
Ho un InputStream di un file e io uso i componenti POI apache di leggere da questo modo:
POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream);
Il problema è che ho bisogno di usare lo stesso flusso più volte e la POIFSFileSystem chiude il flusso dopo l'uso.
Qual è il modo migliore per memorizzare nella cache i dati dal flusso di input e poi servire più flussi di ingresso per diversi POIFSFileSystem?
EDIT 1:
Per la cache volevo dire negozio per un uso successivo, non come un modo per accelerare l'applicazione. Inoltre è meglio leggere solo il flusso di input in un array o una stringa e quindi creare flussi di ingresso per ogni utilizzo?
EDIT 2:
Siamo spiacenti di riaprire la questione, ma le condizioni sono un po 'diverso quando si lavora all'interno di desktop e applicazioni web. Prima di tutto, l'InputStream ottengo dalla org.apache.commons.fileupload.FileItem nel mio tomcat web app non supporta le marcature, quindi, non può reimpostare.
In secondo luogo, mi piacerebbe essere in grado di mantenere il file in memoria per accesso più rapidi e meno problemi io quando si tratta di file.
Soluzione
è possibile decorare InputStream essere passato a POIFSFileSystem con una versione che, quando close () viene chiamato risponde 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();
}
}
testcase
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);
}
Modifica 2
è possibile leggere l'intero file in un byte [] (modalità slurp) per poi passare ad un ByteArrayInputStream
Altri suggerimenti
Prova BufferedInputStream, che aggiunge marchio e ripristinare la funzionalità ad un altro flusso di input, e solo ignorare la sua close:
public class UnclosableBufferedInputStream extends BufferedInputStream {
public UnclosableBufferedInputStream(InputStream in) {
super(in);
super.mark(Integer.MAX_VALUE);
}
@Override
public void close() throws IOException {
super.reset();
}
}
UnclosableBufferedInputStream bis = new UnclosableBufferedInputStream (inputStream);
e utilizzare bis
ovunque inputStream è stato usato prima.
Questo funziona correttamente:
byte[] bytes = getBytes(inputStream);
POIFSFileSystem fileSystem = new POIFSFileSystem(new ByteArrayInputStream(bytes));
dove getBytes è in questo modo:
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();
}
Utilizzare seguito implementazione per un uso più personalizzato -
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();
}
}
}
Che cosa si intende con "cache"? Vuoi la diversa POIFSFileSystem per avviare all'inizio del flusso? Se è così, non c'è assolutamente nessun punto cache nulla nel codice Java; sarà fatto dal sistema operativo, basta aprire un nuovo flusso.
O si wan per continuare a leggere nel punto in cui il primo POIFSFileSystem fermato? Questo non è il caching, ed è molto difficile da fare. L'unico modo che posso pensare se non è possibile evitare il flusso sempre chiusa sarebbe quella di scrivere un involucro sottile che conta quanti byte sono stati letti e quindi aprire un nuovo flusso e ignorare che molti byte. Ma che potrebbe non funzionare quando POIFSFileSystem utilizza internamente qualcosa come un BufferedInputStream.
Se il file non è così grande, leggerlo in una matrice di byte[]
e dare un POI ByteArrayInputStream
creata da tale matrice.
Se il file è grande, allora non si dovrebbe importa, dal momento che il sistema operativo farà il caching per voi come meglio può.
[EDIT] Utilizzare Apache per leggere il file in un array di byte in un maniera efficiente. Non utilizzare int read()
dato che legge il file byte per byte, che è molto lento!
Se si vuole farlo da soli, utilizzare un oggetto File
per ottenere la lunghezza, creare l'array e il un ciclo che legge i byte dal file. È necessario anello dal read(byte[], int offset, int len)
può leggere meno di len
byte (e di solito lo fa).
Questo è come vorrei implementato, per essere utilizzato in modo sicuro con qualsiasi InputStream:
- scrivere il proprio involucro InputStream in cui si crea un file temporaneo per rispecchiare il contenuto flusso originale
- scaricare tutto leggere dal flusso di input originale in questo file temporaneo
- quando il flusso è stato completamente leggere avrete tutti i dati rispecchiati nel file temporaneo
- utilizzare InputStream.reset commutare (inizializzare) il flusso interno di un FileInputStream (mirrored_content_file)
- da ora in poi si perderanno il riferimento del flusso originale (può essere raccolti)
- aggiungere una nuova release () metodo che rimuoverà il file temporaneo e rilasciare qualsiasi flusso aperto.
- si può anche chiamare release () da Finalizza per essere sicuri che il file temporaneo è uscita nel caso in cui si dimentica di chiamare release () (il più delle volte si dovrebbe evitare l'uso di Finalizza , chiamare sempre un metodo per liberare risorse di oggetti). vedi Perché mai implementare finalizzare ()?
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));
}
Questo funziona. IOUtils fa parte dei Comuni IO.
questa risposta itera su quelli precedenti 1 | 2 in base alla BufferInputStream
. Le principali modifiche sono che permette il riutilizzo infinita. E si prende cura di chiudere il flusso di ingresso fonte originale per liberare-up risorse di sistema. Il vostro sistema operativo definisce un limite su quelli e non si vuole che il programma corto di handle di file ( Questo è anche il motivo per cui si dovrebbe sempre 'consumano' risposte ad esempio con l'apache EntityUtils.consumeQuietly()
). Modifica Aggiornato il codice per gestire per i consumatori golosi buffet di antipasti che utilizzano read(buffer, offset, length)
, in questo caso può accadere che BufferedInputStream
si sforza di guardare il sorgente, questo codice protegge contro tale 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();
}
}
}
Per riutilizzare semplicemente chiuderlo prima se non fosse stato.
Una limitazione è però che se il flusso è chiuso prima che l'intero contenuto del flusso originale è stato letto, allora questo decoratore avrà dati incompleti, in modo da assicurarsi che l'intero flusso viene letto prima della chiusura.
Ho solo aggiungere la mia soluzione qui, come questo funziona per me. Si tratta essenzialmente di una combinazione dei primi due risposte:)
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();
}