Cara mudah untuk menulis konten Java InputStream ke OutputStream

445

Saya terkejut menemukan hari ini bahwa saya tidak dapat melacak cara sederhana untuk menulis konten InputStreamke OutputStreamdi Jawa. Jelas, kode buffer byte tidak sulit untuk ditulis, tetapi saya curiga saya hanya melewatkan sesuatu yang akan membuat hidup saya lebih mudah (dan kode lebih jelas).

Jadi, mengingat InputStream indan OutputStream out, apakah ada cara yang lebih sederhana untuk menulis yang berikut ini?

byte[] buffer = new byte[1024];
int len = in.read(buffer);
while (len != -1) {
    out.write(buffer, 0, len);
    len = in.read(buffer);
}
Matt Sheppard
sumber
Anda menyebutkan dalam komentar bahwa ini untuk aplikasi seluler. Apakah itu Android asli? Jika demikian, beri tahu saya dan saya akan mengirim jawaban lain (yang dapat dilakukan adalah satu baris kode di Android).
Jabari

Jawaban:

182

Jawa 9

Sejak Java 9, InputStreamberikan metode yang disebut transferTodengan tanda tangan berikut:

public long transferTo(OutputStream out) throws IOException

Seperti yang dinyatakan dalam dokumentasi , transferToakan:

Membaca semua byte dari aliran input ini dan menulis byte ke aliran output yang diberikan dalam urutan yang dibaca. Saat kembali, aliran input ini akan berada di ujung aliran. Metode ini tidak menutup aliran mana pun.

Metode ini dapat memblokir pembacaan tanpa batas dari aliran input, atau menulis ke aliran output. Perilaku untuk kasus di mana aliran input dan / atau output secara asinkron tertutup, atau utas terputus selama transfer, sangat spesifik input dan output stream, dan karenanya tidak ditentukan

Jadi untuk menulis konten Java InputStreamke OutputStream, Anda dapat menulis:

input.transferTo(output);
Ali Dehghani
sumber
11
Anda harus memilih Files.copysebanyak mungkin. Ini diimplementasikan dalam kode asli dan karenanya bisa lebih cepat. transferToharus digunakan hanya jika kedua aliran bukan FileInputStream / FileOutputStream.
ZhekaKozlov
@ZhekaKozlov Sayangnya Files.copytidak menangani setiap input / output stream tapi itu khusus dirancang untuk berkas sungai.
The Impaler
396

Seperti disebutkan WMR, org.apache.commons.io.IOUtilsdari Apache memiliki metode yang disebut copy(InputStream,OutputStream)yang tidak persis apa yang Anda cari.

Jadi kamu punya:

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

... dalam kode Anda.

Apakah ada alasan Anda menghindarinya IOUtils?

Mikezx6r
sumber
170
Saya menghindarinya untuk aplikasi seluler ini. Saya sedang membangun karena itu akan melipat gandakan ukuran aplikasi untuk menyimpan 5 baris kode yang sangat sedikit.
Jeremy Logan
36
mungkin layak untuk menyebutkannya indan outharus ditutup pada akhir kode di blok akhirnya
basZero
24
@basZero Atau menggunakan coba dengan blok sumber daya.
Warren Dew
1
Atau Anda bisa menulis bungkus sendiri (masuk, keluar) ... (dalam waktu lebih singkat untuk ...)
MikeM
1
Jika Anda sudah menggunakan perpustakaan Guava, Andrejs merekomendasikan kelas ByteStreams di bawah ini. Mirip dengan apa yang IOUtils lakukan, tetapi hindari menambahkan Commons IO ke proyek Anda.
Jim Tough
328

Jika Anda menggunakan Java 7, File (di perpustakaan standar) adalah pendekatan terbaik:

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

Sunting: Tentu saja itu hanya berguna ketika Anda membuat salah satu dari InputStream atau OutputStream dari file. Gunakan file.toPath()untuk mendapatkan jalur dari file.

Untuk menulis ke file yang sudah ada (misalnya yang dibuat dengan File.createTempFile()), Anda harus melewati REPLACE_EXISTINGopsi salin (jika FileAlreadyExistsExceptiontidak dilempar):

Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING)
pengguna1079877
sumber
26
Saya tidak berpikir ini benar-benar menyelesaikan masalah karena salah satu ujungnya adalah jalan. Meskipun Anda bisa mendapatkan jalur untuk file, sejauh yang saya tahu Anda tidak bisa mendapatkan satu untuk aliran umum (mis. Satu melalui jaringan).
Matt Sheppard
4
CopyOptions sewenang-wenang! Anda dapat meletakkannya di sini jika Anda menginginkannya.
user1079877
4
sekarang ini yang saya cari! JDK untuk menyelamatkan, tidak perlu perpustakaan lain
Don Cheadle
7
FYI, FilesTIDAK tersedia di Android 1.7 Java. Saya tersengat oleh ini: stackoverflow.com/questions/24869323/…
Joshua Pinter
23
Yang mengherankan, JDK juga memiliki Files.copy()yang mengambil dua aliran, dan adalah apa yang semua Files.copy()fungsi lain maju untuk melakukan pekerjaan yang sebenarnya menyalin. Namun, itu pribadi (karena sebenarnya tidak melibatkan Paths atau File pada tahap itu), dan terlihat persis seperti kode dalam pertanyaan OP sendiri (ditambah pernyataan pengembalian). Tidak ada pembukaan, tidak ada penutupan, hanya salinan lingkaran.
Ti Strga
102

Saya pikir ini akan berhasil, tetapi pastikan untuk mengujinya ... "perbaikan" kecil, tetapi mungkin sedikit biaya pada keterbacaan.

byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
    out.write(buffer, 0, len);
}
Mike Stone
sumber
26
Saya menyarankan buffer minimal 10KB hingga 100KB. Itu tidak banyak dan dapat mempercepat menyalin data dalam jumlah besar secara luar biasa.
Aaron Digulla
6
Anda mungkin ingin mengatakan while(len > 0)bukan != -1, karena yang terakhir juga bisa mengembalikan 0 saat menggunakan read(byte b[], int off, int len)-metode, yang melempar pengecualian @out.write
phil294
12
@ Belauhirn: Itu tidak benar, karena sepenuhnya sah menurut InputStreamkontrak untuk dibaca untuk mengembalikan 0 beberapa kali. Dan menurut OutputStreamkontrak, metode menulis harus menerima panjang 0, dan hanya membuang pengecualian ketika lennegatif.
Christoffer Hammarström
1
Anda dapat menyimpan garis dengan mengubah whileke fordan meletakkan salah satu variabel di bagian init untuk: misalnya for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);,. =)
ɲeuroburɳ
1
@ Blauhim read()hanya dapat mengembalikan nol jika Anda memberikan panjang nol, yang akan menjadi kesalahan pemrograman, dan kondisi bodoh untuk mengulang selamanya. Dan write()tidak tidak membuang perkecualian jika Anda memberikan panjang nol.
Marquis of Lorne
54

Menggunakan Jambu Biji ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);
Andrejs
sumber
11
Jangan lupa untuk menutup aliran setelah itu!
WonderCsabo
Ini adalah jawaban terbaik jika Anda sudah menggunakan Jambu Biji yang telah menjadi sangat diperlukan bagi saya.
Hong
1
@Hong Anda harus menggunakan Files.copysebanyak mungkin. Gunakan ByteStreams.copyhanya jika kedua aliran bukan FileInputStream / FileOutputStream.
ZhekaKozlov
@ZhekaKozlov Terima kasih atas tipnya. Dalam kasus saya, aliran input berasal dari sumber daya aplikasi Android (dapat ditarik).
Hong
26

Fungsi sederhana

Jika Anda hanya memerlukan ini untuk menulis InputStreamke Filemaka Anda dapat menggunakan fungsi sederhana ini:

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
sumber
4
Fungsi hebat, terima kasih. Akan tetapi, apakah Anda perlu menahan close()panggilan finally?
Joshua Pinter
@ JoshPinter Tidak ada ruginya.
Jordan LaPrise
3
Anda mungkin harus menyertakan blok akhirnya dan tidak menelan pengecualian dalam implementasi aktual. Juga, menutup InputStream yang diteruskan ke suatu metode terkadang tidak terduga dengan metode pemanggilan, jadi orang harus mempertimbangkan apakah itu perilaku yang mereka inginkan.
Cel Skeggs
2
Mengapa menangkap Pengecualian ketika IOException sudah mencukupi?
Prabhakar
18

The JDKpenggunaan kode yang sama sehingga tampak seperti tidak ada "lebih mudah" cara tanpa pihak ketiga perpustakaan kikuk (yang mungkin tidak melakukan apa-apa pula yang berbeda). Yang berikut disalin langsung dari 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
sumber
2
Iya. Malu panggilan khusus ini bersifat pribadi dan tidak ada pilihan lain selain menyalinnya ke kelas utilitas Anda sendiri, karena mungkin Anda tidak berurusan dengan file, melainkan 2 soket sekaligus.
Dragas
17

PipedInputStreamdan PipedOutputStreamseharusnya hanya digunakan ketika Anda memiliki banyak utas, seperti dicatat oleh Javadoc .

Juga, perhatikan bahwa aliran input dan aliran output tidak membungkus gangguan utas dengan IOException... Jadi, Anda harus mempertimbangkan untuk memasukkan kebijakan gangguan ke kode Anda:

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

Ini akan menjadi tambahan yang berguna jika Anda berharap untuk menggunakan API ini untuk menyalin data dalam volume besar, atau data dari stream yang macet untuk waktu yang lama.

Dilum Ranatunga
sumber
14

Bagi mereka yang menggunakan kerangka kerja Spring ada kelas StreamUtils yang berguna :

StreamUtils.copy(in, out);

Di atas tidak menutup aliran. Jika Anda ingin stream ditutup setelah penyalinan, gunakan kelas FileCopyUtils sebagai gantinya:

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

Tidak ada cara untuk melakukan ini lebih mudah dengan metode JDK, tetapi seperti yang telah dicatat Apocalisp, Anda bukan satu-satunya yang memiliki gagasan ini: Anda dapat menggunakan IOUtils dari Jakarta Commons IO , ini juga memiliki banyak hal berguna lainnya, bahwa IMO harus benar-benar menjadi bagian dari JDK ...

WMR
sumber
6

Menggunakan Java7 dan coba-dengan-sumber daya , hadir dengan versi yang disederhanakan dan dapat dibaca.

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
sumber
3
Pembilasan di dalam lingkaran sangat kontraproduktif.
Marquis of Lorne
5

Inilah yang saya lakukan dengan loop paling sederhana.

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
sumber
4

Gunakan kelas Util Commons Net:

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

Cuplikan IMHO lebih minimal (yang juga lebih sempit lingkup variabel panjang):

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

Sebagai catatan tambahan, saya tidak mengerti mengapa lebih banyak orang tidak menggunakan forloop, sebagai gantinya memilih untuk whiledengan ekspresi assign-and-test yang dianggap oleh beberapa orang sebagai gaya "buruk".

Bohemian
sumber
1
Saran Anda menyebabkan penulisan 0 byte pada iterasi pertama. Mungkin paling tidak:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
Brian de Alwis
2
@ BriandeAlwis Anda benar tentang iterasi pertama yang salah. Kode telah diperbaiki (IMHO dengan cara yang lebih bersih daripada saran Anda) - lihat kode yang diedit. Terima kasih untuk merawat.
Bohemian
3

Ini tembakan terbaik saya !!

Dan jangan gunakan inputStream.transferTo(...)karena terlalu generik. Kinerja kode Anda akan lebih baik jika Anda mengontrol memori buffer Anda.

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

Saya menggunakannya dengan metode (improvisasi) ini ketika saya tahu sebelumnya ukuran stream.

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
sumber
2

Saya pikir lebih baik menggunakan buffer besar, karena sebagian besar file lebih besar dari 1024 byte. Juga merupakan praktik yang baik untuk memeriksa jumlah byte yang dibaca menjadi positif.

byte[] buffer = new byte[4096];
int n;
while ((n = in.read(buffer)) > 0) {
    out.write(buffer, 0, n);
}
out.close();
Alexander Volkov
sumber
4
Menggunakan buffer besar memang ide yang baik tetapi bukan karena file sebagian besar> 1k, itu adalah untuk diamortisasi biaya panggilan sistem.
Marquis of Lorne
1

Saya menggunakan BufferedInputStreamdan BufferedOutputStreammenghapus semantik buffering dari kode

try (OutputStream out = new BufferedOutputStream(...);
     InputStream in   = new BufferedInputStream(...))) {
  int ch;
  while ((ch = in.read()) != -1) {
    out.write(ch);
  }
}
Archimedes Trajano
sumber
Mengapa 'menghapus semantik buffering dari kode' adalah ide yang bagus?
Marquis of Lorne
2
Itu berarti saya tidak menulis logika buffering sendiri, saya menggunakan yang dibangun ke JDK yang biasanya cukup baik.
Archimedes Trajano
0

PipedInputStream dan PipedOutputStream mungkin berguna, karena Anda dapat menghubungkan satu ke yang lain.

Arktronic
sumber
1
Ini tidak baik untuk kode single-threaded karena bisa menemui jalan buntu; lihat pertanyaan ini stackoverflow.com/questions/484119/…
Raekye
2
Mungkin ada gunanya bagaimana? Dia sudah memiliki aliran input dan aliran output. Bagaimana tepatnya menambahkan satu sama lain dari setiap bantuan?
Marquis of Lorne
0

Kandidat lain yang mungkin adalah utilitas Jambu I / O:

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

Saya pikir saya akan menggunakan ini karena Guava sudah sangat berguna dalam proyek saya, daripada menambahkan perpustakaan lain untuk satu fungsi.

Andrew Mao
sumber
Ada copydan toByteArraymetode di docs.guava-libraries.googlecode.com/git-history/release/javadoc/… (jambu biji memanggil input / output stream sebagai "byte stream" dan pembaca / penulis sebagai "char streams")
Raekye
jika Anda sudah menggunakan perpustakaan jambu biji itu ide yang baik, tetapi jika tidak, mereka adalah perpustakaan raksasa dengan ribuan metode 'google-cara-melakukan-segala sesuatu yang berbeda dengan standar'. Saya akan menjauh dari mereka
rups
"Mammoth"? 2.7MB dengan satu set dependensi yang sangat kecil, dan API yang dengan hati-hati menghindari duplikasi JDK inti.
Adrian Baker
0

Tidak terlalu mudah dibaca, tetapi efektif, tidak memiliki dependensi dan berjalan dengan versi java apa pun

byte[] buffer = new byte[1024];
for (int n; (n = inputStream.read(buffer)) != -1; outputStream.write(buffer, 0, n));
IPP Nerd
sumber
!= -1atau > 0? Predikat itu tidak persis sama.
The Impaler
! = -1 berarti bukan-dari-file. Ini bukan iterasi tetapi while-do-loop dalam penyamaran: 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
sumber
4
Bisakah Anda jelaskan mengapa ini adalah jawaban yang benar?
rfornal
-6

Anda dapat menggunakan metode ini

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
sumber
6
catch(Exception ex){}- ini adalah yang terbaik
ᄂ ᄀ