Wie klont man einen InputStream?

Ich habe einen InputStream, den ich an eine Methode zur Verarbeitung übergebe. Ich werde denselben InputStream in einer anderen Methode verwenden, aber nach der ersten Verarbeitung wird der InputStream innerhalb der Methode geschlossen angezeigt.

Wie kann ich den InputStream klonen, um ihn an die Methode zu senden, die ihn schließt? Es gibt eine andere Lösung?

EDIT: die Methoden, die den InputStream schließt, ist eine externe Methode aus einer Lib. Ich habe keine Kontrolle über das Schließen oder nicht.

private String getContent(HttpURLConnection con) { InputStream content = null; String charset = ""; try { content = con.getInputStream(); CloseShieldInputStream csContent = new CloseShieldInputStream(content); charset = getCharset(csContent); return IOUtils.toString(content,charset); } catch (Exception e) { System.out.println("Error downloading page: " + e); return null; } } private String getCharset(InputStream content) { try { Source parser = new Source(content); return parser.getEncoding(); } catch (Exception e) { System.out.println("Error determining charset: " + e); return "UTF-8"; } } 

   

Wenn Sie nur dieselben Informationen mehr als einmal lesen möchten und die Eingabedaten klein genug sind, um in den Speicher zu passen, können Sie die Daten von Ihrem InputStream in einen ByteArrayOutputStream kopieren .

Dann können Sie das zugehörige Array von Bytes erhalten und beliebig viele “geklonte” ByteArrayInputStreams öffnen.

 ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Fake code simulating the copy // You can generally do better with nio if you need... // And please, unlike me, do something about the Exceptions :D byte[] buffer = new byte[1024]; int len; while ((len = input.read(buffer)) > -1 ) { baos.write(buffer, 0, len); } baos.flush(); // Open new InputStreams using the recorded bytes // Can be repeated as many times as you wish InputStream is1 = new ByteArrayInputStream(baos.toByteArray()); InputStream is2 = new ByteArrayInputStream(baos.toByteArray()); 

Aber wenn Sie den ursprünglichen Stream wirklich offen halten müssen, um neue Daten zu erhalten, müssen Sie diese externe close() -Methode verfolgen und verhindern, dass sie irgendwie aufgerufen wird.

Sie möchten Apache CloseShieldInputStream :

Dies ist ein Wrapper, der verhindert, dass der Stream geschlossen wird. Du würdest so etwas tun.

 InputStream is = null; is = getStream(); //obtain the stream CloseShieldInputStream csis = new CloseShieldInputStream(is); // call the bad function that does things it shouldn't badFunction(csis); // happiness follows: do something with the original input stream is.read(); 

Sie können es nicht klonen, und wie Sie Ihr Problem lösen, hängt davon ab, was die Quelle der Daten ist.

Eine Lösung besteht darin, alle Daten aus dem InputStream in ein Bytearray einzulesen und dann einen ByteArrayInputStream um dieses Bytearray herum zu erstellen und diesen Eingabestream an Ihre Methode weiterzuleiten.

Edit 1: Das heißt, wenn die andere Methode auch dieselben Daten lesen muss. Dh Sie möchten den Stream “zurücksetzen”.

Wenn die aus dem Stream gelesenen Daten groß sind, würde ich einen TeeInputStream von Apache Commons IO empfehlen. Auf diese Weise können Sie die Eingabe im Wesentlichen replizieren und eine t’d-Pipe als Klon übergeben.

Dies funktioniert möglicherweise nicht in allen Situationen, aber hier ist, was ich getan habe: Ich habe die FilterInputStream- class erweitert und die erforderliche Verarbeitung der Bytes ausgeführt, während die externe Bibliothek die Daten liest.

 public class StreamBytesWithExtraProcessingInputStream extends FilterInputStream { protected StreamBytesWithExtraProcessingInputStream(InputStream in) { super(in); } @Override public int read() throws IOException { int readByte = super.read(); processByte(readByte); return readByte; } @Override public int read(byte[] buffer, int offset, int count) throws IOException { int readBytes = super.read(buffer, offset, count); processBytes(buffer, offset, readBytes); return readBytes; } private void processBytes(byte[] buffer, int offset, int readBytes) { for (int i = 0; i < readBytes; i++) { processByte(buffer[i + offset]); } } private void processByte(int readByte) { // TODO do processing here } } 

Dann übergeben Sie einfach eine Instanz von StreamBytesWithExtraProcessingInputStream Sie im Eingabestream übergeben hätten. Mit dem ursprünglichen Eingabestream als Konstruktorparameter.

Es sollte beachtet werden, dass dies Byte für Byte funktioniert. Verwenden Sie dieses daher nicht, wenn eine hohe performance erforderlich ist.

Wenn Sie apache.commons , können Sie Streams mit IOUtils .

Sie können folgenden Code verwenden:

 InputStream = IOUtils.toBufferedInputStream(toCopy); 

Hier ist das vollständige Beispiel für Ihre Situation:

 public void cloneStream() throws IOException{ InputStream toCopy=IOUtils.toInputStream("aaa"); InputStream dest= null; dest=IOUtils.toBufferedInputStream(toCopy); toCopy.close(); String result = new String(IOUtils.toByteArray(dest)); System.out.println(result); } 

Dieser Code benötigt einige Abhängigkeiten:

MAFEN

  commons-io commons-io 2.4  

GRADLE

 'commons-io:commons-io:2.4' 

Hier ist die DOC-Referenz für diese Methode:

Ruft den gesamten Inhalt eines InputStream ab und stellt dieselben Daten wie das Ergebnis InputStream dar. Diese Methode ist nützlich, wenn

Source InputStream ist langsam. Ihm sind Netzwerkressourcen zugeordnet, so dass wir es nicht lange offen halten können. Es ist Netzwerk-Timeout zugeordnet.

Sie können mehr über IOUtils hier finden: http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStream(java.io.InputStream)

Das Klonen eines Eingabestreams ist möglicherweise keine gute Idee, da dies eine genaue Kenntnis der Details des geklonten Eingabestreams erfordert. Eine Problemumgehung für das Erstellen eines neuen Eingabedatenstroms, der erneut aus derselben Quelle liest, ist eine Problemumgehung.

Mit einigen Java 8 Features würde das so aussehen:

 public class Foo { private Supplier inputStreamSupplier; public void bar() { procesDataThisWay(inputStreamSupplier.get()); procesDataTheOtherWay(inputStreamSupplier.get()); } private void procesDataThisWay(InputStream) { // ... } private void procesDataTheOtherWay(InputStream) { // ... } } 

Diese Methode hat den positiven Effekt, dass Code wieder verwendet wird, der bereits vorhanden ist – die Erstellung des in inputStreamSupplier eingekapselten inputStreamSupplier . Und es ist nicht notwendig, einen zweiten Codepfad für das Klonen des Stroms aufrechtzuerhalten.

Auf der anderen Seite, wenn das Lesen aus dem Stream teuer ist (weil es über eine Verbindung mit niedriger Bandbreite erfolgt), dann wird diese Methode die Kosten verdoppeln. Dies könnte umgangen werden, indem ein bestimmter Lieferant verwendet wird, der den Stream-Inhalt zuerst lokal speichert und einen InputStream für diese nun lokale Ressource bereitstellt.

Unten ist die Lösung mit Kotlin.

Sie können Ihren InputStream in ByteArray kopieren

 val inputStream = ... val byteOutputStream = ByteArrayOutputStream() inputStream.use { input -> byteOutputStream.use { output -> input.copyTo(output) } } val byteInputStream = ByteArrayInputStream(byteOutputStream.toByteArray()) 

Wenn Sie den byteInputStream mehrmals lesen byteInputStream , rufen Sie byteInputStream.reset() bevor Sie erneut lesen.

https://code.luasoftware.com/tutorials/kotlin/how-to-clone-inputstream/

Die class unten sollte den Trick machen. Erstellen Sie einfach eine Instanz, rufen Sie die “multiply” -Methode auf und geben Sie den Quelleingabestream und die Anzahl der Duplikate an, die Sie benötigen.

Wichtig: Sie müssen alle geklonten Streams gleichzeitig in separaten Threads verwenden.

 package foo.bar; import java.io.IOException; import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class InputStreamMultiplier { protected static final int BUFFER_SIZE = 1024; private ExecutorService executorService = Executors.newCachedThreadPool(); public InputStream[] multiply(final InputStream source, int count) throws IOException { PipedInputStream[] ins = new PipedInputStream[count]; final PipedOutputStream[] outs = new PipedOutputStream[count]; for (int i = 0; i < count; i++) { ins[i] = new PipedInputStream(); outs[i] = new PipedOutputStream(ins[i]); } executorService.execute(new Runnable() { public void run() { try { copy(source, outs); } catch (IOException e) { e.printStackTrace(); } } }); return ins; } protected void copy(final InputStream source, final PipedOutputStream[] outs) throws IOException { byte[] buffer = new byte[BUFFER_SIZE]; int n = 0; try { while (-1 != (n = source.read(buffer))) { //write each chunk to all output streams for (PipedOutputStream out : outs) { out.write(buffer, 0, n); } } } finally { //close all output streams for (PipedOutputStream out : outs) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } }