Die effizienteste Methode zum Erstellen von InputStream aus OutputStream

84

Diese Seite: http://blog.ostermiller.org/convert-java-outputstream-inputstream beschreibt, wie ein InputStream aus OutputStream erstellt wird:

new ByteArrayInputStream(out.toByteArray())

Andere Alternativen sind die Verwendung von PipedStreams und neuen Threads, was umständlich ist.

Ich mag die Idee nicht, viele Megabyte in ein neues Speicherbyte-Array zu kopieren. Gibt es eine Bibliothek, die dies effizienter erledigt?

BEARBEITEN:

Auf Anraten von Laurence Gonsalves habe ich PipedStreams ausprobiert und es stellte sich heraus, dass es nicht so schwer ist, mit ihnen umzugehen. Hier ist der Beispielcode 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))
Vagif Verdi
quelle

Antworten:

71

Wenn Sie nicht alle Daten auf einmal in einen In-Memory-Puffer kopieren möchten, müssen Sie Ihren Code haben, der den OutputStream (den Produzenten) verwendet, und den Code, der den InputStream (den Verbraucher) verwendet ) entweder im selben Thread abwechseln oder gleichzeitig in zwei separaten Threads arbeiten. Es ist wahrscheinlich viel komplizierter, wenn sie im selben Thread ausgeführt werden, als wenn zwei separate Threads verwendet werden. Dies ist viel fehleranfälliger (Sie müssen sicherstellen, dass der Verbraucher das Warten auf Eingaben niemals blockiert, oder Sie blockieren effektiv) und würde dies erforderlich machen Produzent und Konsument laufen in derselben Schleife, was viel zu eng miteinander verbunden zu sein scheint.

Verwenden Sie also einen zweiten Thread. Es ist wirklich nicht so kompliziert. Die Seite, auf die Sie verlinkt haben, hatte ein perfektes Beispiel:

  PipedInputStream in = new PipedInputStream();
  PipedOutputStream out = new PipedOutputStream(in);
  new Thread(
    new Runnable(){
      public void run(){
        class1.putDataOnOutputStream(out);
      }
    }
  ).start();
  class2.processDataFromInputStream(in);
Laurence Gonsalves
quelle
Ich denke, Sie müssen auch für jeden Consumer-Thread einen neuen PipedInputStream erstellen. Wenn Sie aus einem anderen Thread aus Pipe lesen, wird ein Fehler angezeigt.
Denis Tulskiy
@Lawrence: Ich verstehe Ihre Gründe für die Verwendung von 2 Threads nicht ... AUSSER es ist erforderlich, dass alle aus dem InputStream gelesenen Zeichen rechtzeitig in den OutputStream geschrieben werden.
Stephen C
8
Stephen: Du kannst etwas nicht lesen, bis es geschrieben wurde. Mit nur einem Thread müssen Sie entweder zuerst alles schreiben (ein großes In-Memory-Array erstellen, das Vagif vermeiden wollte), oder Sie müssen abwechselnd darauf achten, dass der Leser das Warten auf Eingaben niemals blockiert (denn wenn ja) wird der Autor auch nie ausführen können).
Laurence Gonsalves
1
Ist dieser Vorschlag sicher in einer JEE-Umgebung zu verwenden, in der der Container wahrscheinlich viele seiner eigenen Threads ausführt?
Toskan
2
@Toskan Wenn new ThreadIhr Container aus irgendeinem Grund nicht geeignet ist, prüfen Sie, ob Sie einen Thread-Pool verwenden können.
Laurence Gonsalves
14

Es gibt eine weitere Open Source-Bibliothek namens EasyStream , die sich auf transparente Weise mit Pipes und Threads befasst. Das ist nicht wirklich kompliziert, wenn alles gut geht. Probleme treten auf, wenn (am Beispiel von Laurence Gonsalves)

class1.putDataOnOutputStream (out);

Wirft eine Ausnahme. In diesem Beispiel wird der Thread einfach abgeschlossen und die Ausnahme geht verloren, während der äußere InputStreammöglicherweise abgeschnitten wird.

Easystream befasst sich mit der Weitergabe von Ausnahmen und anderen bösen Problemen, die ich seit etwa einem Jahr debugge. (Ich bin der Mantainer der Bibliothek: Offensichtlich ist meine Lösung die beste;)) Hier ist ein Beispiel für die Verwendung:

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

Es gibt auch eine nette Einführung, in der alle anderen Möglichkeiten zum Konvertieren eines OutputStream in einen InputStream erläutert werden. Einen Blick wert.

Gab
quelle
1
Das Tutorial zur Verwendung ihrer Klasse ist unter code.google.com/p/io-tools/wiki/Tutorial_EasyStream
koppor
9

Eine einfache Lösung, die das Kopieren des Puffers vermeidet, besteht darin, einen speziellen Zweck zu erstellen 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);
    }
}

Schreiben Sie nach Bedarf in den obigen Ausgabestream und rufen Sie dann toInputStreamauf, um einen Eingabestream über den zugrunde liegenden Puffer zu erhalten. Betrachten Sie den Ausgabestream nach diesem Punkt als geschlossen.

Eron Wright
quelle
7

Ich denke, der beste Weg, um InputStream mit einem OutputStream zu verbinden, sind Piped-Streams - verfügbar im Paket java.io wie folgt:

// 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.*/

Meiner Meinung nach gibt es zwei Hauptvorteile für diesen Code:

1 - Mit Ausnahme des Puffers wird kein zusätzlicher Speicher verbraucht.

2 - Sie müssen die Datenwarteschlange nicht manuell bearbeiten

Mostafa Abdellateef
quelle
1
Das wäre großartig, aber die Javadocs sagen, wenn Sie diese im selben Thread lesen und schreiben, könnten Sie einen Deadlock bekommen. Ich wünschte, sie hätten dies mit NIO aktualisiert!
Nate Glenn
1

Normalerweise versuche ich zu vermeiden, einen separaten Thread zu erstellen, da die Wahrscheinlichkeit eines Deadlocks, die Schwierigkeit, den Code zu verstehen, und die Probleme beim Umgang mit Ausnahmen zunehmen.

Hier ist meine vorgeschlagene Lösung: ein ProducerInputStream, der durch wiederholte Aufrufe von produzierenChunk () Inhalte in Blöcken erstellt:

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

}
Kennzeichen
quelle