Il modo più efficiente per creare InputStream da OutputStream


86

Questa pagina: http://blog.ostermiller.org/convert-java-outputstream-inputstream descrive come creare un InputStream da OutputStream:

new ByteArrayInputStream(out.toByteArray())

Altre alternative consistono nell'usare PipedStreams e nuovi thread che sono ingombranti.

Non mi piace l'idea di copiare molti megabyte in un nuovo array di byte di memoria. Esiste una libreria che lo faccia in modo più efficiente?

MODIFICARE:

Su consiglio di Laurence Gonsalves, ho provato PipedStreams e si è scoperto che non sono così difficili da affrontare. Ecco il codice di esempio in clojure:

(defn #^PipedInputStream create-pdf-stream [pdf-info]
  (let [in-stream (new PipedInputStream)
        out-stream (PipedOutputStream. in-stream)]
    (.start (Thread. #(;Here you write into out-stream)))
    in-stream))

Risposte:


73

Se non vuoi copiare tutti i dati in un buffer in memoria tutto in una volta, dovrai avere il tuo codice che utilizza OutputStream (il produttore) e il codice che utilizza InputStream (il consumatore ) si alternano nello stesso thread o operano contemporaneamente in due thread separati. Farli funzionare nello stesso thread è probabilmente molto più complicato dell'utilizzo di due thread separati, è molto più soggetto a errori (dovrai assicurarti che il consumatore non si blocchi mai in attesa di input, o sarai effettivamente deadlock) e sarebbe necessario avere il produttore e il consumatore in esecuzione nello stesso ciclo che sembra troppo strettamente accoppiato.

Quindi usa un secondo thread. Non è davvero così complicato. La pagina a cui ti sei collegato aveva un esempio perfetto:

  PipedInputStream in = new PipedInputStream();
  PipedOutputStream out = new PipedOutputStream(in);
  new Thread(
    new Runnable(){
      public void run(){
        class1.putDataOnOutputStream(out);
      }
    }
  ).start();
  class2.processDataFromInputStream(in);

Penso che sia necessario creare anche un nuovo PipedInputStream per ogni thread consumer. Se leggi da Pipe da un altro thread, ti darà un errore.
Denis Tulskiy il

@ Lawrence: Non capisco la tua logica per l'utilizzo di 2 thread ... A MENO CHE non sia un requisito che tutti i caratteri letti da InputStream siano scritti in OutputStream in modo tempestivo.
Stephen C

8
Stephen: non puoi leggere qualcosa finché non è stato scritto. Quindi con un solo thread è necessario scrivere tutto prima (creando un grande array in memoria che Vagif voleva evitare) o è necessario alternarli facendo molta attenzione che il lettore non si blocchi mai in attesa di input (perché se lo fa , lo scrittore non riuscirà mai a eseguire neanche).
Laurence Gonsalves il

1
questo suggerimento è sicuro da usare in un ambiente JEE in cui il contenitore probabilmente esegue molti dei suoi thread?
Toskan

2
@Toskan se new Threadnon è appropriato nel tuo contenitore per qualsiasi motivo, controlla se c'è un pool di thread che puoi usare.
Laurence Gonsalves,

14

Esiste un'altra libreria Open Source chiamata EasyStream che si occupa di pipe e thread in modo trasparente. Non è davvero complicato se tutto va bene. I problemi sorgono quando (guardando l'esempio di Laurence Gonsalves)

class1.putDataOnOutputStream (out);

Genera un'eccezione. In questo esempio il thread viene completato e l'eccezione viene persa, mentre quella esterna InputStreampotrebbe essere troncata.

Easystream si occupa della propagazione delle eccezioni e di altri fastidiosi problemi di cui eseguo il debug da circa un anno. (Sono il mantainer della libreria: ovviamente la mia soluzione è la migliore;)) Ecco un esempio su come usarla:

final InputStreamFromOutputStream<String> isos = new InputStreamFromOutputStream<String>(){
 @Override
 public String produce(final OutputStream dataSink) throws Exception {
   /*
    * call your application function who produces the data here
    * WARNING: we're in another thread here, so this method shouldn't 
    * write any class field or make assumptions on the state of the outer class. 
    */
   return produceMydata(dataSink)
 }
};

C'è anche una bella introduzione in cui vengono spiegati tutti gli altri modi per convertire un OutputStream in un InputStream. Vale la pena dare un'occhiata.


1
Il tutorial per l'utilizzo della loro classe è disponibile su code.google.com/p/io-tools/wiki/Tutorial_EasyStream
koppor

9

Una semplice soluzione che evita di copiare il buffer è creare uno scopo speciale ByteArrayOutputStream:

public class CopyStream extends ByteArrayOutputStream {
    public CopyStream(int size) { super(size); }

    /**
     * Get an input stream based on the contents of this output stream.
     * Do not use the output stream after calling this method.
     * @return an {@link InputStream}
     */
    public InputStream toInputStream() {
        return new ByteArrayInputStream(this.buf, 0, this.count);
    }
}

Scrivere nel flusso di output sopra come necessario, quindi chiamare toInputStreamper ottenere un flusso di input sul buffer sottostante. Considera il flusso di output come chiuso dopo quel punto.


7

Penso che il modo migliore per connettere InputStream a un OutputStream sia tramite flussi in pipe , disponibili nel pacchetto java.io, come segue:

// 1- Define stream buffer
private static final int PIPE_BUFFER = 2048;

// 2 -Create PipedInputStream with the buffer
public PipedInputStream inPipe = new PipedInputStream(PIPE_BUFFER);

// 3 -Create PipedOutputStream and bound it to the PipedInputStream object
public PipedOutputStream outPipe = new PipedOutputStream(inPipe);

// 4- PipedOutputStream is an OutputStream, So you can write data to it
// in any way suitable to your data. for example:
while (Condition) {
     outPipe.write(mByte);
}

/*Congratulations:D. Step 4 will write data to the PipedOutputStream
which is bound to the PipedInputStream so after filling the buffer
this data is available in the inPipe Object. Start reading it to
clear the buffer to be filled again by the PipedInputStream object.*/

Secondo me ci sono due vantaggi principali per questo codice:

1 - Non vi è alcun consumo aggiuntivo di memoria ad eccezione del buffer.

2 - Non è necessario gestire manualmente l'accodamento dei dati


1
Sarebbe fantastico, ma i javadoc dicono che se leggi e scrivi su questi nello stesso thread potresti ottenere un deadlock. Vorrei che lo avessero aggiornato con NIO!
Nate Glenn

1

Di solito cerco di evitare di creare un thread separato a causa della maggiore possibilità di deadlock, della maggiore difficoltà di comprensione del codice e dei problemi di gestione delle eccezioni.

Ecco la mia soluzione proposta: un ProducerInputStream che crea contenuti in blocchi mediante ripetute chiamate a produceChunk ():

public abstract class ProducerInputStream extends InputStream {

    private ByteArrayInputStream bin = new ByteArrayInputStream(new byte[0]);
    private ByteArrayOutputStream bout = new ByteArrayOutputStream();

    @Override
    public int read() throws IOException {
        int result = bin.read();
        while ((result == -1) && newChunk()) {
            result = bin.read();
        }
        return result;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = bin.read(b, off, len);
        while ((result == -1) && newChunk()) {
            result = bin.read(b, off, len);
        }
        return result;
    }

    private boolean newChunk() {
        bout.reset();
        produceChunk(bout);
        bin = new ByteArrayInputStream(bout.toByteArray());
        return (bout.size() > 0);
    }

    public abstract void produceChunk(OutputStream out);

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.