Einfache Möglichkeit, Inhalte eines Java InputStream in einen OutputStream zu schreiben

445

Ich war überrascht, heute festzustellen, dass ich keine einfache Möglichkeit finden konnte, den Inhalt eines zu schreiben InputStream eines OutputStreamin Java . Natürlich ist der Byte-Puffer-Code nicht schwer zu schreiben, aber ich vermute, ich vermisse nur etwas, das mein Leben einfacher machen würde (und den Code klarer macht).

Gibt es also angesichts von InputStream inund OutputStream outeine einfachere Möglichkeit, Folgendes zu schreiben?

byte[] buffer = new byte[1024];
int len = in.read(buffer);
while (len != -1) {
    out.write(buffer, 0, len);
    len = in.read(buffer);
}
Matt Sheppard
quelle
Sie haben in einem Kommentar erwähnt, dass dies für eine mobile App gilt. Ist es natives Android? Wenn ja, lassen Sie es mich wissen und ich werde eine weitere Antwort posten (es kann eine einzelne Codezeile in Android sein).
Jabari

Antworten:

182

Java 9

InputStreamBietet seit Java 9 eine Methode transferTomit der folgenden Signatur:

public long transferTo(OutputStream out) throws IOException

Wie in der Dokumentation angegeben , transferTowird:

Liest alle Bytes aus diesem Eingabestream und schreibt die Bytes in der angegebenen Reihenfolge in den angegebenen Ausgabestream. Bei der Rückkehr befindet sich dieser Eingabestream am Ende des Streams. Diese Methode schließt keinen Stream.

Diese Methode kann das unbegrenzte Lesen aus dem Eingabestream oder das Schreiben in den Ausgabestream blockieren. Das Verhalten für den Fall, dass der Eingabe- und / oder Ausgabestream asynchron geschlossen oder der Thread während der Übertragung unterbrochen wird, ist stark spezifisch für den Eingabe- und Ausgabestream und daher nicht angegeben

Um also den Inhalt eines Java InputStreamin einen zu schreiben OutputStream, können Sie schreiben:

input.transferTo(output);
Ali Dehghani
quelle
11
Sie sollten Files.copyso viel wie möglich bevorzugen . Es ist in nativem Code implementiert und kann daher schneller sein. transferTosollte nur verwendet werden, wenn beide Streams nicht FileInputStream / FileOutputStream sind.
ZhekaKozlov
@ZhekaKozlov Leider Files.copybehandelt nicht alle Eingabe / Ausgabe - Streams , aber es ist speziell für entworfen Datei - Streams.
Der Impaler
396

Wie WMR erwähnt hat, hat org.apache.commons.io.IOUtilsApache eine Methode namens, copy(InputStream,OutputStream)die genau das tut, wonach Sie suchen.

Also hast du:

InputStream in;
OutputStream out;
IOUtils.copy(in,out);
in.close();
out.close();

... in Ihrem Code.

Gibt es einen Grund, den Sie vermeiden IOUtils?

Mikezx6r
quelle
170
Ich vermeide es für diese mobile App, die ich baue, weil sie die Größe der App verfünffacht, um nur 5 Codezeilen zu speichern.
Jeremy Logan
36
es ist vielleicht erwähnenswert inund outmuss am Ende des Codes in einem finally-Block geschlossen werden
basZero
24
@basZero Oder verwenden Sie einen Versuch mit Ressourcenblock.
Warren Dew
1
Oder Sie schreiben einfach Ihren eigenen Wrapper (in, out) ... (in kürzerer Zeit bis ...)
MikeM
1
Wenn Sie bereits die Guava-Bibliothek verwenden, hat Andrejs die folgende ByteStreams-Klasse empfohlen. Ähnlich wie bei IOUtils, jedoch wird das Hinzufügen von Commons IO zu Ihrem Projekt vermieden.
Jim Tough
328

Wenn Sie Java 7 verwenden, ist Dateien (in der Standardbibliothek) der beste Ansatz:

/* You can get Path from file also: file.toPath() */
Files.copy(InputStream in, Path target)
Files.copy(Path source, OutputStream out)

Bearbeiten: Natürlich ist es nur nützlich, wenn Sie einen InputStream oder OutputStream aus einer Datei erstellen. Verwenden Sie file.toPath()diese Option , um den Pfad aus der Datei abzurufen.

Um in eine vorhandene Datei zu schreiben (z. B. eine, die mit erstellt wurde File.createTempFile()), müssen Sie die REPLACE_EXISTINGKopieroption übergeben (andernfalls FileAlreadyExistsExceptionwird sie ausgelöst):

Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING)
user1079877
quelle
26
Ich denke nicht, dass dies das Problem tatsächlich löst, da ein Ende ein Pfad ist. Sie können zwar einen Pfad für eine Datei abrufen, aber meines Wissens können Sie keinen für einen generischen Stream (z. B. einen über das Netzwerk) abrufen.
Matt Sheppard
4
CopyOptions ist beliebig! Sie können es hier setzen, wenn Sie es wollen.
user1079877
4
nun das ist das, was ich gesucht habe! JDK zur Rettung, keine Notwendigkeit für eine weitere Bibliothek
Don Cheadle
7
Zu Ihrer Information, Filesist NICHT in Java 1.7 von Android verfügbar . Ich wurde dadurch gestochen: stackoverflow.com/questions/24869323/…
Joshua Pinter
23
Amüsanterweise hat das JDK auch einen, Files.copy()der zwei Streams benötigt, und auf den sich alle anderen Files.copy()Funktionen beziehen, um die eigentliche Kopierarbeit zu erledigen. Es ist jedoch privat (da es zu diesem Zeitpunkt tatsächlich keine Pfade oder Dateien enthält) und sieht genauso aus wie der Code in der eigenen Frage des OP (plus eine return-Anweisung). Kein Öffnen, kein Schließen, nur eine Kopierschleife.
Ti Strga
102

Ich denke, das wird funktionieren, aber stellen Sie sicher, dass Sie es testen ... geringfügige "Verbesserung", aber es könnte ein bisschen teuer für die Lesbarkeit sein.

byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
    out.write(buffer, 0, len);
}
Mike Stone
quelle
26
Ich schlage einen Puffer von mindestens 10 KB bis 100 KB vor. Das ist nicht viel und kann das Kopieren großer Datenmengen enorm beschleunigen.
Aaron Digulla
6
Vielleicht möchten Sie while(len > 0)stattdessen sagen != -1, da letztere bei Verwendung der read(byte b[], int off, int len)out.write
Methode -method
12
@Blauhirn: Das wäre falsch, da es laut InputStreamVertrag völlig legal ist, dass read 0 beliebig oft zurückgibt . Und laut OutputStreamVertrag muss die Schreibmethode eine Länge von 0 akzeptieren und sollte nur dann eine Ausnahme auslösen, wenn sie lennegativ ist.
Christoffer Hammarström
1
Sie können eine Zeile speichern, indem Sie die whilein a ändern forund eine der Variablen in den Init-Abschnitt von for einfügen: z for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);. =)
ɲeuroburɳ
1
@Blauhim read()kann nur dann Null zurückgeben, wenn Sie eine Länge von Null angegeben haben, was ein Programmierfehler und eine dumme Bedingung wäre, die für immer wiederholt werden muss. Und write()löst keine Ausnahme aus, wenn Sie eine Länge von Null angeben.
Marquis von Lorne
54

Verwenden von Guaven ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);
Andrejs
quelle
11
Vergessen Sie nicht, die Streams danach zu schließen!
WonderCsabo
Dies ist die beste Antwort, wenn Sie bereits Guava verwenden, was für mich unverzichtbar geworden ist.
Hong
1
@Hong Du solltest Files.copyso viel wie möglich verwenden. Verwenden Sie ByteStreams.copynur dann , wenn beide Ströme sind nicht Fileinputstream / Outputstream.
ZhekaKozlov
@ ZhekaKozlov Danke für den Tipp. In meinem Fall stammt der Eingabestream aus der Ressource einer Android-App (zeichnbar).
Hong
26

Einfache Funktion

Wenn Sie dies nur zum Schreiben eines InputStreaman a benötigen File, können Sie diese einfache Funktion verwenden:

private void copyInputStreamToFile( InputStream in, File file ) {
    try {
        OutputStream out = new FileOutputStream(file);
        byte[] buf = new byte[1024];
        int len;
        while((len=in.read(buf))>0){
            out.write(buf,0,len);
        }
        out.close();
        in.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Jordan LaPrise
quelle
4
Tolle Funktion, danke. Müssten Sie die close()Anrufe jedoch in finallyBlöcken platzieren?
Joshua Pinter
@ JoshPinter Es würde nicht schaden.
Jordan LaPrise
3
Sie sollten wahrscheinlich beide einen finally-Block einschließen und keine Ausnahmen in eine tatsächliche Implementierung schlucken. Das Schließen eines an eine Methode übergebenen InputStreams ist für die aufrufende Methode manchmal unerwartet. Daher sollte überlegt werden, ob es sich um das gewünschte Verhalten handelt.
Cel Skeggs
2
Warum die Ausnahme abfangen, wenn die IOException ausreicht?
Prabhakar
18

Das JDKverwendet den gleichen Code, so dass es den Anschein hat, als gäbe es keinen "einfacheren" Weg ohne klobige Bibliotheken von Drittanbietern (die wahrscheinlich sowieso nichts anderes machen). Folgendes wird direkt kopiert von java.nio.file.Files.java:

// buffer size used for reading and writing
private static final int BUFFER_SIZE = 8192;

/**
  * Reads all bytes from an input stream and writes them to an output stream.
  */
private static long copy(InputStream source, OutputStream sink) throws IOException {
    long nread = 0L;
    byte[] buf = new byte[BUFFER_SIZE];
    int n;
    while ((n = source.read(buf)) > 0) {
        sink.write(buf, 0, n);
        nread += n;
    }
    return nread;
}
BullyWiiPlaza
quelle
2
Ja. Schade, dass dieser bestimmte Aufruf privat ist und es keine andere Möglichkeit gibt, als ihn in Ihre eigene Dienstprogrammklasse zu kopieren, da es möglich ist, dass Sie nicht mit Dateien, sondern mit zwei Sockets gleichzeitig arbeiten.
Dragas
17

PipedInputStreamund PipedOutputStreamsollte nur verwendet werden, wenn Sie mehrere Threads haben, wie vom Javadoc angegeben .

Beachten Sie außerdem, dass Eingabestreams und Ausgabestreams keine Thread-Unterbrechungen mit IOExceptions umschließen. Sie sollten daher in Betracht ziehen, eine Unterbrechungsrichtlinie in Ihren Code aufzunehmen:

byte[] buffer = new byte[1024];
int len = in.read(buffer);
while (len != -1) {
    out.write(buffer, 0, len);
    len = in.read(buffer);
    if (Thread.interrupted()) {
        throw new InterruptedException();
    }
}

Dies ist eine nützliche Ergänzung, wenn Sie diese API zum Kopieren großer Datenmengen oder von Daten aus Streams verwenden möchten, die unerträglich lange hängen bleiben.

Dilum Ranatunga
quelle
14

Für diejenigen, die das Spring Framework verwenden, gibt es eine nützliche StreamUtils- Klasse:

StreamUtils.copy(in, out);

Das Obige schließt die Streams nicht. Wenn Sie möchten, dass die Streams nach dem Kopieren geschlossen werden, verwenden Sie stattdessen die FileCopyUtils- Klasse:

FileCopyUtils.copy(in, out);
holmis83
quelle
8

Es gibt keine Möglichkeit, dies mit JDK-Methoden viel einfacher zu machen, aber wie Apocalisp bereits bemerkt hat, sind Sie nicht der einzige mit dieser Idee: Sie könnten IOUtils von Jakarta Commons IO verwenden , es hat auch viele andere nützliche Dinge, dass IMO eigentlich Teil des JDK sein sollte ...

WMR
quelle
6

Mit Java7 und Try-with-Resources wird eine vereinfachte und lesbare Version geliefert .

try(InputStream inputStream = new FileInputStream("C:\\mov.mp4");
    OutputStream outputStream = new FileOutputStream("D:\\mov.mp4")) {

    byte[] buffer = new byte[10*1024];

    for (int length; (length = inputStream.read(buffer)) != -1; ) {
        outputStream.write(buffer, 0, length);
    }
} catch (FileNotFoundException exception) {
    exception.printStackTrace();
} catch (IOException ioException) {
    ioException.printStackTrace();
}
Sivakumar
quelle
3
Das Spülen innerhalb der Schleife ist äußerst kontraproduktiv.
Marquis von Lorne
5

Hier kommt, wie ich es mit der einfachsten for-Schleife mache.

private void copy(final InputStream in, final OutputStream out)
    throws IOException {
    final byte[] b = new byte[8192];
    for (int r; (r = in.read(b)) != -1;) {
        out.write(b, 0, r);
    }
}
Jin Kwon
quelle
4

Verwenden Sie die Util-Klasse von Commons Net:

import org.apache.commons.net.io.Util;
...
Util.copyStream(in, out);
DejanLekic
quelle
3

Ein meiner Meinung nach minimaleres Snippet (das auch die Längenvariable enger zusammenfasst):

byte[] buffer = new byte[2048];
for (int n = in.read(buffer); n >= 0; n = in.read(buffer))
    out.write(buffer, 0, n);

Als Randnotiz verstehe ich nicht, warum mehr Leute keine forSchleife verwenden, sondern sich für einen whilemit einem Zuweisungs- und Testausdruck entscheiden, der von einigen als "schlechter" Stil angesehen wird.

Böhmisch
quelle
1
Ihr Vorschlag führt bei der ersten Iteration zu einem 0-Byte-Schreibvorgang. Vielleicht am wenigsten tun:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
Brian de Alwis
2
@BriandeAlwis Sie haben Recht damit, dass die erste Iteration falsch ist. Der Code wurde korrigiert (IMHO sauberer als Ihr Vorschlag) - siehe bearbeiteten Code. Danke für die Fürsorge.
Bohemian
3

Das ist mein bester Schuss !!

Und nicht verwenden, inputStream.transferTo(...)weil es zu allgemein ist. Ihre Codeleistung ist besser, wenn Sie Ihren Pufferspeicher steuern.

public static void transfer(InputStream in, OutputStream out, int buffer) throws IOException {
    byte[] read = new byte[buffer]; // Your buffer size.
    while (0 < (buffer = in.read(read)))
        out.write(read, 0, buffer);
}

Ich benutze es mit dieser (verbesserungsfähigen) Methode, wenn ich die Größe des Streams im Voraus kenne.

public static void transfer(int size, InputStream in, OutputStream out) throws IOException {
    transfer(in, out,
            size > 0xFFFF ? 0xFFFF // 16bits 65,536
                    : size > 0xFFF ? 0xFFF// 12bits 4096
                            : size < 0xFF ? 0xFF // 8bits 256
                                    : size
    );
}
Daniel De León
quelle
2

Ich denke, es ist besser, einen großen Puffer zu verwenden, da die meisten Dateien größer als 1024 Bytes sind. Es ist auch eine gute Praxis, die Anzahl der gelesenen Bytes zu überprüfen, um positiv zu sein.

byte[] buffer = new byte[4096];
int n;
while ((n = in.read(buffer)) > 0) {
    out.write(buffer, 0, n);
}
out.close();
Alexander Volkov
quelle
4
Die Verwendung eines großen Puffers ist in der Tat eine gute Idee, aber nicht, da Dateien meistens> 1 KB groß sind, sondern um die Kosten für Systemaufrufe zu amortisieren.
Marquis von Lorne
1

Ich benutze BufferedInputStreamund BufferedOutputStream, um die Puffersemantik aus dem Code zu entfernen

try (OutputStream out = new BufferedOutputStream(...);
     InputStream in   = new BufferedInputStream(...))) {
  int ch;
  while ((ch = in.read()) != -1) {
    out.write(ch);
  }
}
Archimedes Trajano
quelle
Warum ist das Entfernen der Puffersemantik aus dem Code eine gute Idee?
Marquis von Lorne
2
Das heißt, ich schreibe die Pufferlogik nicht selbst, sondern verwende die im JDK integrierte, die normalerweise gut genug ist.
Archimedes Trajano
0

PipedInputStream und PipedOutputStream können von Nutzen sein, da Sie eine Verbindung miteinander herstellen können.

Arktronic
quelle
1
Dies ist nicht gut für Single-Threaded-Code, da dies zu einem Deadlock führen kann. siehe diese Frage stackoverflow.com/questions/484119/…
Raekye
2
Kann von Nutzen sein, wie? Er hat bereits einen Eingabestream und einen Ausgabestream. Wie kann das Hinzufügen eines weiteren von jedem genau helfen?
Marquis von Lorne
0

Ein weiterer möglicher Kandidat sind die Guava I / O-Dienstprogramme:

http://code.google.com/p/guava-libraries/wiki/IOExplained

Ich dachte, ich würde diese verwenden, da Guava in meinem Projekt bereits sehr nützlich ist, anstatt eine weitere Bibliothek für eine Funktion hinzuzufügen.

Andrew Mao
quelle
Es gibt copyund toByteArrayMethoden in docs.guava-libraries.googlecode.com/git-history/release/javadoc/… (Guave nennt Eingabe- / Ausgabestreams als "Byte-Streams" und Leser / Writer als "Char-Streams")
Raekye
Wenn Sie bereits Guavenbibliotheken verwenden, ist dies eine gute Idee. Wenn nicht, handelt es sich um eine Mammutbibliothek mit Tausenden von Methoden. Ich würde mich von ihnen
fernhalten
"Mammut"? 2,7 MB mit einer sehr kleinen Anzahl von Abhängigkeiten und einer API, die das Duplizieren des Kern-JDK sorgfältig vermeidet.
Adrian Baker
0

Nicht sehr lesbar, aber effektiv, hat keine Abhängigkeiten und läuft mit jeder Java-Version

byte[] buffer = new byte[1024];
for (int n; (n = inputStream.read(buffer)) != -1; outputStream.write(buffer, 0, n));
IPP Nerd
quelle
!= -1oder > 0? Diese Prädikate sind nicht ganz gleich.
Der Impaler
! = -1 bedeutet Nicht-Dateiende. Dies ist keine Iteration, sondern eine verschleierte while-do-Schleife: while ((n = inputStream.read (buffer))! = -1) do {outputStream.write (buffer, 0, n)}
IPP Nerd
-1
public static boolean copyFile(InputStream inputStream, OutputStream out) {
    byte buf[] = new byte[1024];
    int len;
    long startTime=System.currentTimeMillis();

    try {
        while ((len = inputStream.read(buf)) != -1) {
            out.write(buf, 0, len);
        }

        long endTime=System.currentTimeMillis()-startTime;
        Log.v("","Time taken to transfer all bytes is : "+endTime);
        out.close();
        inputStream.close();

    } catch (IOException e) {

        return false;
    }
    return true;
}
Nour Rteil
quelle
4
Können Sie bitte erklären, warum dies die richtige Antwort ist?
rfornal
-6

Sie können diese Methode verwenden

public static void copyStream(InputStream is, OutputStream os)
 {
     final int buffer_size=1024;
     try
     {
         byte[] bytes=new byte[buffer_size];
         for(;;)
         {
           int count=is.read(bytes, 0, buffer_size);
           if(count==-1)
               break;
           os.write(bytes, 0, count);
         }
     }
     catch(Exception ex){}
 }
Pranav
quelle
6
catch(Exception ex){}- das ist erstklassig
25 ᄀ