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.

È stato utile?

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();
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top