Modo semplice per scrivere i contenuti di un InputStream Java in un OutputStream


445

Sono stato sorpreso di scoprire oggi che non sono riuscito a rintracciare un modo semplice per scrivere il contenuto di an InputStreamin an OutputStreamin Java. Ovviamente, il codice del buffer di byte non è difficile da scrivere, ma sospetto che mi manchi qualcosa che mi renderebbe la vita più semplice (e il codice più chiaro).

Quindi, dati un InputStream ine un OutputStream out, c'è un modo più semplice per scrivere quanto segue?

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

In un commento hai menzionato che questo è per un'app mobile. È Android nativo? In tal caso, fammi sapere e posterò un'altra risposta (si può fare è una singola riga di codice in Android).
Jabari,

Risposte:


182

Java 9

Da Java 9, InputStreamfornisce un metodo chiamato transferTocon la seguente firma:

public long transferTo(OutputStream out) throws IOException

Come indica la documentazionetransferTo :

Legge tutti i byte da questo flusso di input e li scrive nel flusso di output specificato nell'ordine in cui sono letti. Al ritorno, questo flusso di input sarà alla fine del flusso. Questo metodo non chiude nessuno dei flussi.

Questo metodo può bloccare la lettura indefinita dal flusso di input o la scrittura nel flusso di output. Il comportamento nel caso in cui il flusso di input e / o output sia chiuso in modo asincrono, o il thread interrotto durante il trasferimento, è altamente specifico del flusso di input e output, e quindi non specificato

Quindi, per scrivere i contenuti di un Java InputStreamsu un OutputStream, è possibile scrivere:

input.transferTo(output);

11
Dovresti preferire Files.copyil più possibile. È implementato nel codice nativo e quindi può essere più veloce. transferTodovrebbe essere usato solo se entrambi i flussi non sono FileInputStream / FileOutputStream.
Zheka Kozlov,

@ZhekaKozlov Sfortunatamente Files.copynon gestisce alcun flusso di input / output ma è specificamente progettato per i flussi di file .
The Impaler

396

Come menzionato WMR, org.apache.commons.io.IOUtilsda Apache ha un metodo chiamato copy(InputStream,OutputStream)che fa esattamente quello che stai cercando.

Quindi hai:

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

... nel tuo codice.

C'è un motivo che stai evitando IOUtils?


170
Lo sto evitando per questa app mobile che sto costruendo perché quintuplicerebbe le dimensioni dell'app per salvare un misero 5 righe di codice.
Jeremy Logan,

36
forse vale la pena menzionarlo ine outdeve essere chiuso alla fine del codice in un blocco
finally

24
@basZero O usando un tentativo con blocco risorse.
Warren Dew,

1
Oppure potresti semplicemente scrivere la tua copia (in, out) wrapper ... (in meno tempo per ...)
MikeM

1
Se stai già utilizzando la libreria Guava, Andrejs ha raccomandato la classe ByteStreams di seguito. Simile a quello che fa IOUtils, ma evita di aggiungere Commons IO al tuo progetto.
Jim Tough,

328

Se si utilizza Java 7, File (nella libreria standard) è l'approccio migliore:

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

Modifica: ovviamente è utile solo quando si crea uno di InputStream o OutputStream da un file. Utilizzare file.toPath()per ottenere il percorso dal file.

Per scrivere in un file esistente (ad esempio uno creato con File.createTempFile()), devi passare l' REPLACE_EXISTINGopzione di copia (altrimenti FileAlreadyExistsExceptionviene generata):

Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING)

26
Non penso che questo risolva effettivamente il problema poiché un'estremità è un percorso. Sebbene sia possibile ottenere un percorso per un file, per quanto ne so non è possibile ottenere uno per qualsiasi flusso generico (ad esempio uno sulla rete).
Matt Sheppard,

4
CopyOptions è arbitrario! Puoi metterlo qui se lo vuoi.
user1079877

4
ora questo è quello che stavo cercando! JDK in soccorso, non c'è bisogno di un'altra biblioteca
Don Cheadle il

7
Cordiali saluti, FilesNON è disponibile in Java 1.7 di Android . Sono rimasto colpito da questo: stackoverflow.com/questions/24869323/…
Joshua Pinter

23
In modo divertente, il JDK ha anche un Files.copy()che accetta due flussi, ed è ciò che tutte le altre Files.copy()funzioni in avanti per fare l'effettivo lavoro di copia. Tuttavia, è privato (poiché in realtà non coinvolge Paths o Files in quella fase) e assomiglia esattamente al codice nella domanda dell'OP (più un'istruzione return). Nessuna apertura, nessuna chiusura, solo un ciclo di copia.
Ti Strga,

102

Penso che funzionerà, ma assicurati di testarlo ... "miglioramento" minore, ma potrebbe essere un po 'un costo in leggibilità.

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

26
Suggerisco un buffer di almeno 10 KB a 100 KB. Non è molto e può accelerare la copia di grandi quantità di dati in modo tremendo.
Aaron Digulla,

6
potresti voler dire while(len > 0)invece di != -1, perché quest'ultimo potrebbe anche restituire 0 quando si utilizza il read(byte b[], int off, int len)metodo, che genera un'eccezione @out.write
phil294

12
@Blauhirn: sarebbe errato, in quanto è completamente legale secondo il InputStreamcontratto per la lettura di restituire 0 un numero qualsiasi di volte. E in base al OutputStreamcontratto, il metodo di scrittura deve accettare una lunghezza di 0 e deve generare un'eccezione solo quando lenè negativo.
Christoffer Hammarström,

1
Puoi salvare una riga cambiando whilein a fore inserendo una delle variabili nella sezione init di for: es for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);. =)
ɲeuroburɳ

1
@Blauhim read()può restituire zero solo se hai fornito una lunghezza pari a zero, che sarebbe un errore di programmazione e una condizione stupida su cui eseguire il ciclo per sempre. E write()non non un'eccezione se si fornisce una lunghezza pari a zero.
Marchese di Lorne,

54

Utilizzando Guava ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);

11
Non dimenticare di chiudere i flussi dopo!
WonderCsabo

Questa è la risposta migliore se stai già usando Guava che è diventato indispensabile per me.
Hong

1
@Hong Dovresti usare Files.copyil più possibile. Utilizzare ByteStreams.copysolo se entrambi i flussi non sono FileInputStream / FileOutputStream.
Zheka Kozlov,

@ZhekaKozlov Grazie per il suggerimento. Nel mio caso, il flusso di input proviene dalla risorsa di un'app Android (disegnabile).
Hong

26

Funzione semplice

Se hai solo bisogno di questo per scrivere un InputStreamin Fileallora puoi usare questa semplice funzione:

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

4
Ottima funzione, grazie. Dovresti mettere le close()chiamate in finallyblocchi, però?
Joshua Pinter,

@JoshPinter Non farebbe male.
Jordan LaPrise,

3
Probabilmente dovresti includere entrambi un blocco finally e non ingoiare eccezioni in un'implementazione effettiva. Inoltre, la chiusura di un InputStream passato a un metodo è talvolta imprevista dal metodo chiamante, quindi si dovrebbe considerare se è il comportamento che vogliono.
Cel Skeggs,

2
Perché cogliere l'eccezione quando IOException è sufficiente?
Prabhakar,

18

Gli JDKusi lo stesso codice così sembra che non v'è alcun modo "facile" senza librerie di terze parti goffo (che probabilmente non fa nulla di diverso in ogni caso). Quanto segue viene copiato direttamente da 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;
}

2
Sì. Peccato che questa chiamata particolare sia privata e non ci sia altra opzione che copiarla nella propria classe di utilità, poiché è possibile che non si tratti di file, ma piuttosto di 2 socket contemporaneamente.
Dragas,

17

PipedInputStreame PipedOutputStreamdovrebbe essere usato solo quando hai più thread, come notato da Javadoc .

Inoltre, tieni presente che i flussi di input e di output non racchiudono alcuna interruzione di thread con IOExceptions ... Quindi, dovresti considerare di incorporare una politica di interruzione nel tuo codice:

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

Questa sarebbe un'aggiunta utile se si prevede di utilizzare questa API per copiare grandi volumi di dati o dati da flussi che rimangono bloccati per un periodo intollerabilmente lungo.


14

Per coloro che usano il framework Spring c'è un'utile classe StreamUtils :

StreamUtils.copy(in, out);

Quanto sopra non chiude i flussi. Se vuoi che gli stream vengano chiusi dopo la copia, utilizza invece la classe FileCopyUtils :

FileCopyUtils.copy(in, out);

8

Non c'è modo di farlo molto più facilmente con i metodi JDK, ma come ha già notato Apocalisp, non sei l'unico con questa idea: potresti usare IOUtils di Jakarta Commons IO , ma ha anche molte altre cose utili, che l'IMO dovrebbe effettivamente far parte del JDK ...


6

Utilizzando Java7 e provare con le risorse , viene fornita una versione semplificata e leggibile.

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

3
Il lavaggio all'interno del circuito è altamente controproducente.
Marchese di Lorne,

5

Ecco come sto andando con il più semplice per loop.

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

4

Usa la classe Util di Commons Net:

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

3

Uno snippet più minimale di IMHO (che ha anche una portata più stretta della variabile lunghezza):

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

Come nota a margine, non capisco perché più persone non usano un forciclo, invece optando per un whilecon un'espressione di assegnazione e prova che è considerata da alcuni come uno stile "povero".


1
Il tuo suggerimento provoca una scrittura di 0 byte sulla prima iterazione. Forse meno:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
Brian de Alwis,

2
@BriandeAlwis Hai ragione sul fatto che la prima iterazione non è corretta. Il codice è stato corretto (IMHO in un modo più pulito del tuo suggerimento) - vedi il codice modificato. Grazie per la cura.
Boemo

3

Questo è il mio miglior scatto !!

E non usare inputStream.transferTo(...)perché è troppo generico. Le prestazioni del codice saranno migliori se controlli la memoria buffer.

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

Lo uso con questo metodo (migliorabile) quando conosco in anticipo le dimensioni del flusso.

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

2

Penso che sia meglio usare un buffer di grandi dimensioni, perché la maggior parte dei file è superiore a 1024 byte. Inoltre è buona norma verificare che il numero di byte letti sia positivo.

byte[] buffer = new byte[4096];
int n;
while ((n = in.read(buffer)) > 0) {
    out.write(buffer, 0, n);
}
out.close();

4
L'uso di un buffer di grandi dimensioni è davvero una buona idea, ma non perché i file siano per lo più> 1k, è ammortizzare il costo delle chiamate di sistema.
Marchese di Lorne,

1

Uso BufferedInputStreame BufferedOutputStreamper rimuovere la semantica di buffering dal codice

try (OutputStream out = new BufferedOutputStream(...);
     InputStream in   = new BufferedInputStream(...))) {
  int ch;
  while ((ch = in.read()) != -1) {
    out.write(ch);
  }
}

Perché "rimuovere la semantica del buffer dal codice" è una buona idea?
Marchese di Lorne,

2
Significa che non scrivo da solo la logica di buffering, uso quella integrata nel JDK che di solito è abbastanza buona.
Archimede Trajano il

0

PipedInputStream e PipedOutputStream possono essere di qualche utilità, in quanto è possibile connettersi l'uno all'altro.


1
Questo non è buono per il codice a thread singolo in quanto potrebbe deadlock; vedi questa domanda stackoverflow.com/questions/484119/...
Raekye

2
Potrebbe essere utile come? Ha già un flusso di input e un flusso di output. In che modo aggiungere esattamente un altro di ciascun aiuto esattamente?
Marchese di Lorne,

0

Un altro possibile candidato sono le utility di I / O di Guava:

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

Ho pensato di usarli poiché Guava è già immensamente utile nel mio progetto, piuttosto che aggiungere un'altra libreria per una funzione.


Ci sono copye toByteArraymetodi in docs.guava-libraries.googlecode.com/git-history/release/javadoc/… (guava chiama flussi di input / output come "flussi di byte" e lettori / scrittori come "flussi di char")
Raekye

se usi già le librerie guava è una buona idea, ma in caso contrario, sono una libreria gigantesca con migliaia di metodi "google-way-of-doing-tutto-diverso-allo-standard".
Starei

"mammut"? 2,7 MB con un set molto piccolo di dipendenze e un'API che evita attentamente la duplicazione del JDK principale.
Adrian Baker,

0

Non molto leggibile, ma efficace, non ha dipendenze e funziona con qualsiasi versione di Java

byte[] buffer = new byte[1024];
for (int n; (n = inputStream.read(buffer)) != -1; outputStream.write(buffer, 0, n));

!= -1o > 0? Quei predicati non sono esattamente gli stessi.
The Impaler

! = -1 significa non fine del file. Questa non è una iterazione ma un while-do-loop sotto mentite spoglie: 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;
}

4
Puoi spiegare perché questa è la risposta giusta?
rfornal,


-6

puoi usare questo metodo

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

6
catch(Exception ex){}- questo è di prim'ordine
ᄂ ᄀ
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.