วิธีง่ายๆในการเขียนเนื้อหาของ Java InputStream ไปยัง OutputStream


445

ฉันประหลาดใจที่พบว่าวันนี้ฉันไม่สามารถติดตามวิธีง่ายๆในการเขียนเนื้อหาของ a InputStreamไปยังOutputStreamใน Java เห็นได้ชัดว่ารหัสบัฟเฟอร์ไบต์ไม่ยากที่จะเขียน แต่ฉันสงสัยว่าฉันเพิ่งหายไปบางสิ่งบางอย่างที่จะทำให้ชีวิตของฉันง่ายขึ้น (และรหัสชัดเจนขึ้น)

ดังนั้นเมื่อได้รับInputStream inและOutputStream outมีวิธีที่ง่ายกว่าในการเขียนต่อไปนี้หรือไม่?

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

คุณพูดถึงความคิดเห็นว่านี่เป็นแอพมือถือ มันเป็น Android พื้นเมืองหรือไม่ ถ้าเป็นเช่นนั้นให้ฉันรู้และฉันจะโพสต์คำตอบอื่น (สามารถทำได้คือรหัสบรรทัดเดียวใน Android)
Jabari

คำตอบ:


182

Java 9

ตั้งแต่ Java 9 InputStreamให้วิธีการที่เรียกว่าtransferToมีลายเซ็นต่อไปนี้:

public long transferTo(OutputStream out) throws IOException

ในฐานะที่เป็นเอกสารประกอบของรัฐtransferToจะ:

อ่านไบต์ทั้งหมดจากอินพุตสตรีมนี้และเขียนไบต์ไปยังเอาต์พุตสตรีมที่กำหนดตามลำดับที่ถูกอ่าน เมื่อกลับมาสตรีมอินพุตนี้จะสิ้นสุดที่สตรีม วิธีนี้ไม่ได้ปิดสตรีมอย่างใดอย่างหนึ่ง

วิธีนี้อาจบล็อกการอ่านอย่างไม่มีกำหนดเวลาจากอินพุตสตรีมหรือเขียนไปยังเอาต์พุตสตรีม ลักษณะการทำงานสำหรับกรณีที่อินพุตและ / หรือสตรีมเอาต์พุตถูกปิดแบบอะซิงโครนัสหรือเธรดถูกขัดจังหวะในระหว่างการถ่ายโอนเป็นสตรีมอินพุตและเอาท์พุตสูงโดยเฉพาะดังนั้นจึงไม่ได้ระบุ

ดังนั้นในการเขียนเนื้อหาของ Java InputStreamไปยัง an OutputStreamคุณสามารถเขียน:

input.transferTo(output);

11
คุณควรเลือกFiles.copyให้มากที่สุด มันถูกใช้งานในรหัสเนทีฟและดังนั้นจึงสามารถเร็วขึ้น transferToควรใช้เฉพาะในกรณีที่สตรีมทั้งสองไม่ใช่ FileInputStream / FileOutputStream
ZhekaKozlov

@ZhekaKozlov แต่น่าเสียดายที่Files.copyไม่ได้จัดการกับสตรีมอินพุต / เอาต์พุตใด ๆแต่มันถูกออกแบบมาโดยเฉพาะสำหรับสตรีมไฟล์
The Impaler

396

ดังที่ WMR กล่าวไว้org.apache.commons.io.IOUtilsจาก Apache มีวิธีการที่เรียกว่าcopy(InputStream,OutputStream)ทำสิ่งที่คุณต้องการอย่างแท้จริง

ดังนั้นคุณมี:

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

... ในรหัสของคุณ

มีเหตุผลที่คุณหลีกเลี่ยงIOUtilsหรือไม่?


170
ฉันกำลังหลีกเลี่ยงสำหรับแอพมือถือที่ฉันกำลังสร้างอยู่เพราะมันจะทำให้ขนาดของแอปพลิเคชันห้าเท่าสามารถบันทึกรหัสได้ 5 บรรทัด
Jeremy Logan

36
มันอาจคุ้มค่าที่จะกล่าวถึงinและoutต้องปิดท้ายรหัสในบล็อกสุดท้าย
basZero

24
@basZero หรือใช้การลองกับบล็อกทรัพยากร
Warren Dew

1
หรือคุณสามารถเขียนตัวห่อ (เข้า, ออก) ของคุณเอง ... (ในเวลาน้อยกว่าที่จะนำไปใช้กับ ... )
MikeM

1
หากคุณใช้ไลบรารี Guava อยู่แล้ว Andrejs ได้แนะนำคลาส ByteStreams ด้านล่าง คล้ายกับสิ่งที่ IOUtils ทำ แต่หลีกเลี่ยงการเพิ่ม Commons IO ให้กับโครงการของคุณ
Jim Tough

328

หากคุณใช้ Java 7 ไฟล์ (ในไลบรารีมาตรฐาน) เป็นวิธีที่ดีที่สุด:

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

แก้ไข: แน่นอนมันมีประโยชน์เมื่อคุณสร้าง InputStream หรือ OutputStream จากไฟล์ ใช้file.toPath()เพื่อรับพา ธ จากไฟล์

หากต้องการเขียนลงในไฟล์ที่มีอยู่ (เช่นไฟล์ที่สร้างด้วยFile.createTempFile()) คุณจะต้องผ่านREPLACE_EXISTINGตัวเลือกการคัดลอก (มิฉะนั้นFileAlreadyExistsExceptionจะถูกโยนทิ้ง):

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

26
ฉันไม่คิดว่านี่จะแก้ปัญหาได้จริงเนื่องจากปลายด้านหนึ่งเป็นเส้นทาง ในขณะที่คุณสามารถหาพา ธ สำหรับไฟล์ได้ตราบใดที่ฉันทราบว่าคุณไม่สามารถหาพา ธ สำหรับสตรีมทั่วไปใด ๆ (เช่นหนึ่งผ่านเครือข่าย)
Matt Sheppard

4
CopyOptions โดยพลการ! คุณสามารถวางไว้ที่นี่หากคุณต้องการ
user1079877

4
ตอนนี้นี่คือสิ่งที่ฉันกำลังมองหา! JDK เพื่อช่วยเหลือไม่จำเป็นต้องมีห้องสมุดอื่น
Don Cheadle

7
FYI Filesไม่สามารถใช้ได้ในJava 1.7 ของAndroid ฉันได้รับการต่อยโดยสิ่งนี้: stackoverflow.com/questions/24869323/…
Joshua Pinter

23
อย่างน่าประหลาดใจ JDK ก็มีFiles.copy()สตรีมสองสายและเป็นสิ่งที่ทุกFiles.copy()ฟังก์ชั่นอื่น ๆส่งต่อเพื่อที่จะทำงานที่แท้จริงของการคัดลอก อย่างไรก็ตามมันเป็นแบบส่วนตัว (เนื่องจากไม่เกี่ยวข้องกับ Paths หรือ Files ในขั้นตอนนั้น) และดูเหมือนว่าจะเหมือนกับรหัสในคำถามของ OP (รวมถึงคำสั่ง return) ไม่มีการเปิดไม่มีการปิดเพียงการวนรอบการคัดลอก
Ti Strga

102

ฉันคิดว่ามันจะใช้งานได้ แต่ให้แน่ใจว่าได้ทดสอบ ... การปรับปรุงเล็ก ๆ น้อย ๆ แต่มันอาจจะมีค่าใช้จ่ายในการอ่าน

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

26
ฉันแนะนำบัฟเฟอร์อย่างน้อย 10KB ถึง 100KB ไม่มากและสามารถเพิ่มความเร็วในการคัดลอกข้อมูลจำนวนมากได้อย่างมาก
Aaron Digulla

6
คุณอาจต้องการที่จะพูดwhile(len > 0)แทน!= -1เพราะหลังสามารถกลับ 0 เมื่อใช้วิธีการread(byte b[], int off, int len)ที่โยนข้อยกเว้น @out.write
phil294

12
@Blauhirn: นั่นจะไม่ถูกต้องตามกฎหมายอย่างสมบูรณ์ตามInputStreamสัญญาสำหรับการอ่านเพื่อกลับ 0 จำนวนเท่าใดก็ได้ และตามOutputStreamสัญญาวิธีการเขียนจะต้องยอมรับความยาว 0 และควรทิ้งข้อยกเว้นเมื่อlenมีค่าเป็นลบเท่านั้น
Christoffer Hammarström

1
คุณสามารถบันทึกบรรทัดโดยการเปลี่ยนwhileไปforและวางหนึ่งในตัวแปรในสำหรับ init ส่วน: for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);เช่น =)
ɲeuroburɳ

1
@Blauhim read()สามารถส่งกลับค่าศูนย์ได้ถ้าคุณระบุความยาวเป็นศูนย์ซึ่งจะเป็นข้อผิดพลาดในการเขียนโปรแกรมและเงื่อนไขที่โง่ที่จะวนซ้ำตลอดไป และwrite()จะไม่ส่งข้อยกเว้นหากคุณให้ความยาวเป็นศูนย์
มาร์ควิสแห่ง Lorne

54

ใช้ของ Guava ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);

11
อย่าลืมปิดสตรีมหลังจากนั้น!
WonderCsabo

นี่เป็นคำตอบที่ดีที่สุดถ้าคุณใช้ Guava อยู่แล้วซึ่งเป็นสิ่งที่ขาดไม่ได้สำหรับฉัน
ฮ่องกง

1
@Hong คุณควรใช้Files.copyให้มากที่สุด ใช้ByteStreams.copyเฉพาะในกรณีที่สตรีมทั้งสองไม่ใช่ FileInputStream / FileOutputStream
ZhekaKozlov

@ZhekaKozlov ขอบคุณสำหรับเคล็ดลับ ในกรณีของฉันกระแสข้อมูลอินพุทมาจากทรัพยากรของแอป Android (วาดได้)
Hong

26

ฟังก์ชั่นที่เรียบง่าย

หากคุณต้องการเพียงแค่นี้สำหรับการเขียนInputStreamไปยัง a Fileแล้วคุณสามารถใช้ฟังก์ชั่นง่าย ๆ นี้:

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
ฟังก์ชั่นที่ยอดเยี่ยมขอบคุณ คุณจำเป็นต้องวางclose()สายเป็นfinallyบล็อคหรือไม่?
Joshua Pinter

@JoshPinter มันจะไม่เจ็บ
Jordan LaPrise

3
คุณทั้งสองควรรวมบล็อกสุดท้ายและไม่กลืนข้อยกเว้นในการใช้งานจริง นอกจากนี้การปิด InputStream ที่ส่งผ่านไปยังเมธอดบางครั้งก็ไม่คาดคิดโดยวิธีการเรียกดังนั้นควรพิจารณาว่าเป็นพฤติกรรมที่พวกเขาต้องการหรือไม่
Cel Skeggs

2
ทำไมต้องจับข้อยกเว้นเมื่อ IOException เพียงพอ?
Prabhakar

18

JDKใช้รหัสเดียวกันดังนั้นจึงดูเหมือนว่าไม่มี "ง่ายขึ้น" วิธีโดยไม่ต้องห้องสมุดของบุคคลที่สาม clunky (ซึ่งอาจจะไม่ได้ทำอะไรอยู่แล้วแตกต่างกัน) ต่อไปนี้คัดลอกโดยตรงจาก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
ใช่ อับอายสายนี้โดยเฉพาะเป็นส่วนตัวและไม่มีตัวเลือกอื่นนอกจากคัดลอกลงในคลาสยูทิลิตี้ของคุณเองเพราะเป็นไปได้ว่าคุณไม่ได้จัดการกับไฟล์
Dragas

17

PipedInputStreamและPipedOutputStreamควรจะใช้เฉพาะเมื่อคุณมีหลายหัวข้อเช่นที่ระบุไว้โดย Javadoc

นอกจากนี้โปรดทราบว่าอินพุตสตรีมและสตรีมเอาท์พุตไม่ห่อการขัดจังหวะเธรดใด ๆ ด้วยIOExceptions ... ดังนั้นคุณควรพิจารณารวมนโยบายการขัดจังหวะเข้ากับโค้ดของคุณ:

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

นี่จะเป็นการเพิ่มที่มีประโยชน์หากคุณคาดว่าจะใช้ API นี้สำหรับการคัดลอกข้อมูลจำนวนมากหรือข้อมูลจากสตรีมที่ค้างอยู่เป็นเวลานาน


14

สำหรับผู้ที่ใช้Spring frameworkมีคลาสStreamUtils ที่มีประโยชน์:

StreamUtils.copy(in, out);

ด้านบนไม่ปิดสตรีม หากคุณต้องการปิดกระแสข้อมูลหลังจากคัดลอกให้ใช้คลาสFileCopyUtilsแทน:

FileCopyUtils.copy(in, out);

8

ไม่มีวิธีที่จะทำสิ่งนี้ได้ง่ายขึ้นด้วยวิธีการของ JDK แต่อย่างที่ Apocalisp ได้ระบุไว้แล้วคุณไม่ใช่คนเดียวที่มีความคิดนี้: คุณสามารถใช้IOUtilsจากJakarta Commons IOมันยังมีสิ่งที่มีประโยชน์อื่นอีกมากมาย IMO นั้นควรเป็นส่วนหนึ่งของ JDK ...


6

การใช้ Java7 และลองกับทรัพยากรมาพร้อมกับเวอร์ชันที่ง่ายและอ่านได้

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
การล้างภายในลูปนั้นมีประสิทธิภาพในการตอบโต้สูง
มาร์ควิสแห่ง Lorne

5

นี่คือวิธีที่ฉันทำกับลูปที่ง่ายที่สุด

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


3

ตัวอย่างขนาดเล็กที่สุดของ IMHO (ซึ่งก็คือขอบเขตความยาวตัวแปรที่แคบลงเช่นกัน):

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

ในฐานะที่เป็นบันทึกด้านข้างฉันไม่เข้าใจว่าทำไมคนจำนวนมากไม่ใช้forลูปแทนที่จะเลือกใช้whileนิพจน์ที่กำหนดและทดสอบซึ่งบางคนคิดว่าเป็นสไตล์ "ไม่ดี"


1
ข้อเสนอแนะของคุณทำให้เขียน 0 ไบต์ในการทำซ้ำครั้งแรก อย่างน้อยก็ทำได้:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
Brian de Alwis

2
@BriandeAlwis คุณถูกต้องเกี่ยวกับการวนซ้ำครั้งแรกไม่ถูกต้อง รหัสได้รับการแก้ไขแล้ว (IMHO เป็นวิธีที่สะอาดกว่าคำแนะนำของคุณ) - ดูรหัสที่แก้ไขแล้ว ขอบคุณสำหรับการดูแล
โบฮีเมียน

3

นี่คือช็อตที่ดีที่สุดของฉัน !!

และอย่าใช้inputStream.transferTo(...)เพราะเป็นเรื่องธรรมดาเกินไป ประสิทธิภาพโค้ดของคุณจะดีขึ้นหากคุณควบคุมหน่วยความจำบัฟเฟอร์

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

ฉันใช้กับวิธีนี้ (แก้ไขได้) เมื่อฉันรู้ขนาดของสตรีมล่วงหน้า

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

ฉันคิดว่าการใช้บัฟเฟอร์ขนาดใหญ่จะดีกว่าเพราะไฟล์ส่วนใหญ่มีขนาดใหญ่กว่า 1024 ไบต์ นอกจากนี้ยังเป็นแนวปฏิบัติที่ดีในการตรวจสอบจำนวนไบต์ที่อ่านว่าเป็นค่าบวก

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

4
การใช้บัฟเฟอร์ขนาดใหญ่ย่อมเป็นความคิดที่ดี แต่ไม่ใช่เพราะไฟล์ส่วนใหญ่> 1k เป็นค่าใช้จ่ายในการตัดจำหน่ายระบบ
มาร์ควิสแห่ง Lorne

1

ฉันใช้BufferedInputStreamและBufferedOutputStreamเพื่อลบซีแมนทิกส์บัฟเฟอร์ออกจากโค้ด

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

ทำไมการลบความหมายของการบัฟเฟอร์ออกจากรหัสจึงเป็นความคิดที่ดี
มาร์ควิสแห่ง Lorne

2
หมายความว่าฉันไม่ได้เขียนลอจิกบัฟเฟอร์ด้วยตัวเองฉันใช้อันที่สร้างไว้ใน JDK ซึ่งมักจะดีพอ
Archimedes Trajano

0

PipedInputStream และ PipedOutputStream อาจมีการใช้งานบางอย่างเนื่องจากคุณสามารถเชื่อมต่อหนึ่งไปยังอีก


1
สิ่งนี้ไม่ดีสำหรับโค้ดแบบเธรดเดียวเนื่องจากอาจหยุดชะงักได้ ดูคำถามนี้stackoverflow.com/questions/484119/…
Raekye

2
อาจมีประโยชน์อย่างไรบ้าง? เขามีสตรีมอินพุตและสตรีมเอาต์พุตแล้ว จะเพิ่มอีกหนึ่งวิธีในแต่ละวิธีได้อย่างไร
Marquis of Lorne

0

ตัวเลือกอื่น ๆ ที่เป็นไปได้คือยูทิลิตี้ Guava I / O:

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

ฉันคิดว่าฉันใช้สิ่งเหล่านี้เนื่องจาก Guava มีประโยชน์อย่างมากในโครงการของฉันแทนที่จะเพิ่มอีกไลบรารีหนึ่งสำหรับฟังก์ชันหนึ่ง


มีcopyและtoByteArrayวิธีการในdocs.guava-luments.googlecode.com/git-history/release/javadoc/ ...... (ฝรั่งเรียกกระแสอินพุต / เอาต์พุตเป็น "ไบต์สตรีม" และผู้อ่าน / นักเขียนเป็น "ถ่านสตรีม")
Raekye

หากคุณใช้ไลบรารีฝรั่งอยู่แล้วเป็นความคิดที่ดี แต่ถ้าไม่ใช่ก็เป็นห้องสมุดช้างแมมมอ ธ ที่มีวิธีการนับพันวิธี ฉันจะอยู่ห่างจากพวกเขา
rupps

"แมมมอ ธ"? 2.7MB พร้อมชุดการพึ่งพาขนาดเล็กมากและ API ที่หลีกเลี่ยงการทำซ้ำ JDK หลักอย่างระมัดระวัง
Adrian Baker

0

อ่านไม่ได้มาก แต่มีประสิทธิภาพไม่มีการพึ่งพาและทำงานกับ java เวอร์ชันใด ๆ

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

!= -1หรือ> 0? เพรดิเคตเหล่านั้นไม่เหมือนกัน
Impaler

! = -1 หมายถึง not-end-of-file นี่ไม่ใช่การวนซ้ำ แต่เป็น while-do-loop ปลอมตัว: while ((n = inputStream.read (บัฟเฟอร์))! = -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
คุณช่วยอธิบายได้ไหมว่าทำไมคำตอบนี้ถึงถูกต้อง?
rfornal


-6

คุณสามารถใช้วิธีนี้

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){}- นี่คือสุดยอด
ᄂ ᄀ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.