Cách dễ dàng để viết nội dung của Java InputStream vào OutputStream


445

Hôm nay tôi rất ngạc nhiên khi thấy rằng tôi không thể theo dõi bất kỳ cách đơn giản nào để viết nội dung của một InputStreamthành OutputStreamJava. Rõ ràng, mã bộ đệm byte không khó viết, nhưng tôi nghi ngờ rằng tôi chỉ thiếu một cái gì đó sẽ giúp cuộc sống của tôi dễ dàng hơn (và mã rõ ràng hơn).

Vì vậy, đưa ra một InputStream invà một OutputStream out, có cách nào đơn giản hơn để viết như sau?

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

Bạn đã đề cập trong một bình luận rằng đây là một ứng dụng di động. Có phải Android gốc? Nếu vậy, hãy cho tôi biết và tôi sẽ đăng một câu trả lời khác (có thể được thực hiện là một dòng mã trong Android).
Jabari

Câu trả lời:


182

Java 9

Kể từ Java 9, InputStreamcung cấp một phương thức được gọi transferTovới chữ ký sau:

public long transferTo(OutputStream out) throws IOException

Như tài liệu nêu, transferTosẽ:

Đọc tất cả các byte từ luồng đầu vào này và ghi các byte vào luồng đầu ra đã cho theo thứ tự mà chúng được đọc. Khi trở lại, luồng đầu vào này sẽ ở cuối luồng. Phương pháp này không đóng một trong hai luồng.

Phương pháp này có thể chặn việc đọc vô thời hạn từ luồng đầu vào hoặc ghi vào luồng đầu ra. Hành vi cho trường hợp luồng đầu vào và / hoặc đầu ra bị đóng không đồng bộ hoặc luồng bị gián đoạn trong quá trình truyền, là luồng đầu vào và đầu ra rất cụ thể và do đó không được chỉ định

Vì vậy, để viết nội dung của Java InputStreamsang một OutputStream, bạn có thể viết:

input.transferTo(output);

11
Bạn nên thích Files.copycàng nhiều càng tốt. Nó được thực hiện trong mã gốc và do đó có thể nhanh hơn. transferTochỉ nên được sử dụng nếu cả hai luồng không phải là FileInputStream / FileOutputStream.
ZhekaKozlov

@ZhekaKozlov Thật không may Files.copy, không xử lý bất kỳ luồng đầu vào / đầu ra nào nhưng nó được thiết kế riêng cho các luồng tệp .
The Impaler

396

Như WMR đã đề cập, org.apache.commons.io.IOUtilstừ Apache có một phương thức được gọi là copy(InputStream,OutputStream)chính xác những gì bạn đang tìm kiếm.

Vì vậy, bạn có:

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

... trong mã của bạn.

Có một lý do bạn đang tránh IOUtils?


170
Tôi đang tránh ứng dụng này cho ứng dụng di động này. Tôi đang xây dựng vì nó làm tăng kích thước của ứng dụng để tiết kiệm 5 dòng mã.
Jeremy Logan

36
có lẽ đáng để đề cập đến điều đó inoutphải được đóng ở cuối mã trong một khối cuối cùng
basZero

24
@basZero Hoặc sử dụng thử với khối tài nguyên.
Warren Dew

1
Hoặc bạn chỉ có thể viết trình bao bọc bản sao (vào, ra) của riêng bạn ... (trong thời gian ngắn hơn để ...)
MikeM

1
Nếu bạn đã sử dụng thư viện Guava, Andrejs đã đề xuất lớp ByteStreams bên dưới. Tương tự như những gì IOUtils làm, nhưng tránh thêm Commons IO vào dự án của bạn.
Jim Tough

328

Nếu bạn đang sử dụng Java 7, Tệp (trong thư viện chuẩn) là cách tiếp cận tốt nhất:

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

Chỉnh sửa: Tất nhiên nó chỉ hữu ích khi bạn tạo một trong InputStream hoặc OutputStream từ tệp. Sử dụng file.toPath()để có được đường dẫn từ tập tin.

Để ghi vào một tệp hiện có (ví dụ: tệp được tạo bằng File.createTempFile()), bạn sẽ cần phải vượt qua REPLACE_EXISTINGtùy chọn sao chép (nếu không FileAlreadyExistsExceptionsẽ bị ném):

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

26
Tôi không nghĩ rằng điều này thực sự giải quyết vấn đề vì một đầu là một con đường. Mặc dù bạn có thể nhận được đường dẫn cho một tệp, theo như tôi biết, bạn không thể có một đường dẫn cho bất kỳ luồng chung nào (ví dụ: một đường truyền qua mạng).
Matt Sheppard

4
CopyOptions là tùy ý! Bạn có thể đặt nó ở đây nếu bạn muốn nó.
dùng1079877

4
bây giờ đây là những gì tôi đang tìm kiếm! JDK để giải cứu, không cần thư viện khác
Don Cheadle

7
FYI, FilesKHÔNG khả dụng trong Java 1.7 của Android . Tôi đã bị choáng bởi điều này: stackoverflow.com/questions/24869323/ Kẻ
Joshua Pinter

23
Thật thú vị, JDK cũng có một Files.copy()luồng có hai luồng và là điều mà tất cả các Files.copy()chức năng khác chuyển tiếp để thực hiện công việc sao chép thực tế. Tuy nhiên, nó là riêng tư (vì nó không thực sự liên quan đến Đường dẫn hoặc Tệp ở giai đoạn đó) và trông giống hệt mã trong câu hỏi của chính OP (cộng với câu lệnh trả về). Không mở, không đóng, chỉ là một vòng lặp sao chép.
Ti Strga

102

Tôi nghĩ rằng điều này sẽ làm việc, nhưng đảm bảo kiểm tra nó ... "cải thiện" nhỏ, nhưng nó có thể là một chút chi phí ở mức dễ đọc.

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

26
Tôi đề nghị một bộ đệm ít nhất 10KB đến 100KB. Đó không phải là nhiều và có thể tăng tốc độ sao chép lượng lớn dữ liệu.
Aaron Digulla

6
bạn có thể muốn nói while(len > 0)thay != -1vì bởi vì cái sau cũng có thể trả về 0 khi sử dụng read(byte b[], int off, int len)-method, đưa ra một ngoại lệ @out.write
phil294

12
@Blauhirn: Điều đó sẽ không chính xác, vì nó hoàn toàn hợp pháp theo InputStreamhợp đồng đã đọc để trả về 0 bất kỳ số lần nào. Và theo OutputStreamhợp đồng, phương thức ghi phải chấp nhận độ dài bằng 0 và chỉ nên ném ngoại lệ khi lenâm.
Christoffer Hammarström

1
Bạn có thể lưu một dòng bằng cách thay đổi whilethành a forvà đặt một trong các biến vào phần init: vd for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);. =)
ɲeuroburɳ 24/07/2015

1
@Blauhim read()chỉ có thể trả về 0 nếu bạn cung cấp độ dài bằng 0, đó sẽ là lỗi lập trình và là điều kiện ngu ngốc để lặp lại mãi mãi. Và write()không không ném một ngoại lệ nếu bạn cung cấp một chiều dài bằng không.
Hầu tước Lorne

54

Sử dụng quả ổi ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);

11
Đừng quên đóng các luồng sau đó!
WonderCsabo

Đây là câu trả lời tốt nhất nếu bạn đang sử dụng Guava, thứ đã trở nên không thể thiếu đối với tôi.
Hồng

1
@Hong Bạn nên sử dụng Files.copycàng nhiều càng tốt. ByteStreams.copyChỉ sử dụng nếu cả hai luồng không phải là FileInputStream / FileOutputStream.
ZhekaKozlov

@ZhekaKozlov Cảm ơn bạn cho tiền boa. Trong trường hợp của tôi, luồng đầu vào là từ tài nguyên của ứng dụng Android (có thể rút ra).
Hồng

26

Chức năng đơn giản

Nếu bạn chỉ cần điều này để viết InputStreammột Filethì bạn có thể sử dụng chức năng đơn giản này:

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
Chức năng tuyệt vời, cảm ơn. Bạn sẽ cần phải đặt các close()cuộc gọi trong finallykhối, mặc dù?
Joshua Pinter

@JoshPinter Nó sẽ không đau.
Jordan LaPawn

3
Có lẽ cả hai nên bao gồm một khối cuối cùng và không nuốt ngoại lệ trong một triển khai thực tế. Ngoài ra, việc đóng InputStream được truyền cho một phương thức đôi khi không mong muốn bằng phương thức gọi, vì vậy người ta nên xem xét liệu đó có phải là hành vi mà họ muốn không.
Cel Skeggs

2
Tại sao bắt ngoại lệ khi IOException đủ?
Bohhakar

18

Mã này JDKsử dụng cùng một mã để có vẻ như không có cách nào "dễ dàng hơn" nếu không có các thư viện bên thứ ba lộn xộn (có thể không làm gì khác đi). Sau đây được sao chép trực tiếp từ 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
Đúng vậy Xấu hổ cuộc gọi cụ thể này là riêng tư và không có lựa chọn nào khác ngoài việc sao chép nó vào lớp tiện ích của riêng bạn, vì có thể bạn không xử lý các tệp, mà là 2 ổ cắm cùng một lúc.
Dragas

17

PipedInputStreamPipedOutputStreamchỉ nên được sử dụng khi bạn có nhiều luồng, như được ghi nhận bởi Javadoc .

Ngoài ra, lưu ý rằng luồng đầu vào và luồng đầu ra không bao gồm bất kỳ gián đoạn luồng nào với IOExceptions ... Vì vậy, bạn nên xem xét kết hợp chính sách gián đoạn với mã của mình:

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

Đây sẽ là một bổ sung hữu ích nếu bạn muốn sử dụng API này để sao chép khối lượng dữ liệu lớn hoặc dữ liệu từ các luồng bị kẹt trong một thời gian dài không thể chịu đựng được.


14

Đối với những người sử dụng Spring framework, có một lớp StreamUtils hữu ích :

StreamUtils.copy(in, out);

Ở trên không đóng luồng. Nếu bạn muốn các luồng được đóng sau khi sao chép, hãy sử dụng lớp FileCopyUtils :

FileCopyUtils.copy(in, out);

8

Không có cách nào để thực hiện việc này dễ dàng hơn nhiều với các phương thức JDK, nhưng như Apocalisp đã lưu ý, bạn không phải là người duy nhất có ý tưởng này: Bạn có thể sử dụng IOUtils từ Jakarta Commons IO , nó cũng có rất nhiều điều hữu ích khác, IMO thực sự nên là một phần của JDK ...


6

Sử dụng Java7 và dùng thử với các tài nguyên , đi kèm với một phiên bản đơn giản và dễ đọc.

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
Rửa bên trong vòng lặp là phản tác dụng cao.
Hầu tước Lorne

5

Đây là cách tôi đang làm với vòng lặp đơn giản nhất.

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

Sử dụng lớp Util của Commons Net:

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

3

Một đoạn trích IMHO tối thiểu hơn (cũng thu hẹp phạm vi biến số độ dài hơn):

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

Là một lưu ý phụ, tôi không hiểu tại sao nhiều người không sử dụng forvòng lặp, thay vào đó, chọn một whilebiểu thức gán và kiểm tra được một số người coi là kiểu "nghèo".


1
Đề xuất của bạn gây ra một ghi 0 byte trong lần lặp đầu tiên. Có lẽ ít nhất làm:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
Brian de Alwis

2
@BriandeAlwis Bạn nói đúng về lần lặp đầu tiên không chính xác. Mã đã được sửa (IMHO theo cách sạch hơn đề xuất của bạn) - xem mã được chỉnh sửa. Thx cho chăm sóc.
Bohemian

3

Đây là bức ảnh đẹp nhất của tôi !!

Và không sử dụng inputStream.transferTo(...)vì quá chung chung. Hiệu suất mã của bạn sẽ tốt hơn nếu bạn kiểm soát bộ nhớ đệm của mình.

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

Tôi sử dụng nó với phương pháp (ngẫu hứng) này khi tôi biết trước kích thước của luồng.

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

Tôi nghĩ tốt hơn là sử dụng bộ đệm lớn, vì hầu hết các tệp đều lớn hơn 1024 byte. Ngoài ra, đó là một cách thực hành tốt để kiểm tra số byte đọc là dương.

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

4
Sử dụng một bộ đệm lớn thực sự là một ý tưởng tốt nhưng không phải vì các tệp chủ yếu> 1k, mà là để khấu hao chi phí của các cuộc gọi hệ thống.
Hầu tước Lorne

1

Tôi sử dụng BufferedInputStreamBufferedOutputStreamđể loại bỏ ngữ nghĩa đệm từ mã

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

Tại sao 'loại bỏ ngữ nghĩa đệm khỏi mã' là một ý tưởng tốt?
Hầu tước Lorne

2
Điều đó có nghĩa là tôi không tự viết logic đệm, tôi sử dụng logic được tích hợp trong JDK thường đủ tốt.
Archimedes Trajano

0

PipedInputStream và PipedOutputStream có thể được sử dụng, vì bạn có thể kết nối cái này với cái kia.


1
Điều này không tốt cho mã đơn luồng vì nó có thể bế tắc; xem câu hỏi này stackoverflow.com/questions/484119/
Mạnh

2
Có thể được sử dụng như thế nào? Anh ta đã có một luồng đầu vào và một luồng đầu ra. Làm thế nào sẽ thêm một trong những trợ giúp chính xác?
Hầu tước Lorne

0

Một ứng cử viên khác có thể là các tiện ích I / O của Guava:

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

Tôi nghĩ rằng tôi sẽ sử dụng những thứ này vì Guava đã vô cùng hữu ích trong dự án của tôi, thay vì thêm một thư viện khác cho một chức năng.


copytoByteArrayphương thức trong docs.guava-lologists.googlecode.com/git-history/release/javadoc/ mẹo (ổi gọi luồng đầu vào / đầu ra là "luồng byte" và trình đọc / ghi là "luồng char")
Raekye

nếu bạn đã sử dụng thư viện ổi thì đó là một ý tưởng hay, nhưng nếu không, chúng là một thư viện voi ma mút với hàng ngàn phương pháp 'google-cách-làm-mọi thứ-khác-theo-tiêu chuẩn'. Tôi sẽ tránh xa họ
rupps

"voi ma mút"? 2,7 MB với một bộ phụ thuộc rất nhỏ và API tránh cẩn thận sao chép JDK lõi.
Adrian Baker

0

Không dễ đọc lắm, nhưng hiệu quả, không phụ thuộc và chạy với bất kỳ phiên bản java nào

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

!= -1hay > 0? Những vị ngữ không hoàn toàn giống nhau.
The Impaler

! = -1 có nghĩa là không kết thúc tập tin. Đây không phải là lần lặp mà là vòng lặp while-do-ngụy trang: 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
Bạn có thể vui lòng giải thích tại sao đây là câu trả lời đúng?
rfornal


-6

bạn có thể sử dụng phương pháp này

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){}- đây là đỉnh cao
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.