Java InputStream öğesinin içeriğini OutputStream öğesine yazmanın kolay yolu


445

Ben bir içeriğini yazmak için herhangi basit bir yol izini olamayacağını bugün bulmak için şaşırdı InputStreambir etmek OutputStreamJava. Açıkçası, bayt arabellek kodu yazmak zor değil, ama sadece hayatımı kolaylaştıracak bir şey (ve kod daha net) eksik olduğundan şüpheleniyorum.

Öyleyse, a InputStream inve a verildiğinde OutputStream out, aşağıdakileri yazmanın daha basit bir yolu var mı?

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

Bir yorumda bunun bir mobil uygulama için olduğunu belirttiniz. Yerel Android mi? Eğer öyleyse, bana bildirin ve başka bir cevap göndereceğim (Android'de tek bir kod satırı yapılabilir).
Jabari

Yanıtlar:


182

Java 9

Java 9'dan beri , aşağıdaki imzayla InputStreamçağrılan bir yöntem sağlar transferTo:

public long transferTo(OutputStream out) throws IOException

Gibi belgeler devletler, transferToyapacaktır:

Bu giriş akışındaki tüm baytları okur ve baytları okudukları sıraya göre verilen çıkış akışına yazar. Dönüşte, bu giriş akışı akışın sonunda olacaktır. Bu yöntem her iki akışı da kapatmaz.

Bu yöntem, giriş akışından süresiz olarak okumayı veya çıkış akışına yazmayı engelleyebilir. Giriş ve / veya çıkış akışının eşzamansız olarak kapatıldığı veya aktarım sırasında iş parçacığının kesildiği durumun davranışı, yüksek oranda giriş ve çıkış akışına özgüdür ve bu nedenle belirtilmez

Yani bir Java içeriğini yazmak için InputStreambir karşı OutputStreamyazabilirsiniz:

input.transferTo(output);

11
Files.copyMümkün olduğunca tercih etmelisiniz . Yerel kodda uygulanır ve bu nedenle daha hızlı olabilir. transferToyalnızca her iki akış da FileInputStream / FileOutputStream değilse kullanılmalıdır.
ZhekaKozlov

@ZhekaKozlov Maalesef herhangi bir giriş / çıkış akışını Files.copyişlemez, ancak özellikle dosya akışları için tasarlanmıştır .
Impaler

396

WMR'nin belirttiği gibi org.apache.commons.io.IOUtils, Apache'den copy(InputStream,OutputStream)tam olarak aradığınızı yapan bir yöntem var .

Yani, var:

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

... kodunuzda.

Kaçınılması için bir neden var mı IOUtils?


170
Ben bu mobil uygulama için kaçınıyorum çünkü kod büyük ölçüde 5 satır kaydetmek için uygulamanın boyutunu beş katına neden olur.
Jeremy Logan

36
belki de bahsetmeye değer inve outnihayet bir blokta kodun sonunda kapatılmalıdır
basZero

24
@basZero Veya bir kaynak bloğu ile deneyin
Warren Dew

1
(İçin ... sürer az zamanda) Ya da sadece sarıcı ... (dış) kendi kopyasını yazabilirsiniz
MikeM

1
Zaten Guava kütüphanesini kullanıyorsanız, Andrejs aşağıdaki ByteStreams sınıfını tavsiye etti. IOUtils'in yaptığı gibi, ancak projenize Commons IO eklemekten kaçınır.
Jim Tough

328

Java 7 kullanıyorsanız, Dosyalar (standart kitaplıkta) en iyi yaklaşımdır:

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

Düzenleme: Tabii ki sadece dosyadan InputStream veya OutputStream birini oluşturduğunuzda yararlıdır. file.toPath()Dosyadan yol almak için kullanın .

Mevcut bir dosyaya yazmak için (örn. İle oluşturulmuş bir dosya File.createTempFile()), REPLACE_EXISTINGkopyalama seçeneğini (aksi takdirde FileAlreadyExistsExceptionatılır) geçmeniz gerekir :

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

26
Bir ucu bir yol olduğundan bunun aslında sorunu çözdüğünü sanmıyorum. Bir dosya için bir yol alabilirsiniz, ancak bildiğim kadarıyla herhangi bir genel akış için (örneğin ağ üzerinden bir tane) alamazsınız.
Matt Sheppard

4
CopyOptions isteğe bağlıdır! İsterseniz buraya koyabilirsiniz.
user1079877

4
şimdi aradığım şey bu ! JDK kurtarmaya, başka bir kütüphaneye gerek yok
Don Cheadle

7
FYI, Android'in Java 1.7 Filessürümünde mevcut DEĞİLDİR . Bununla şaşırdım: stackoverflow.com/questions/24869323/…
Joshua Pinter

23
Eğlenceli bir şekilde, JDK'nın Files.copy()iki akışı olan bir tane vardır Files.copy()ve gerçek kopyalama işini yapmak için diğer tüm işlevlerin ileri sürdüğü şeydir . Ancak, (aslında bu aşamada Yollar ya Files içermeyen beri) özeldir ve bakar aynen OP'ın kendi Söz konusu kod (artı bir dönüş deyimi) gibi. Açılış yok, kapanış yok, sadece kopya döngüsü var.
Ti Strga

102

Bu işe yarayacağını düşünüyorum, ama test etmek için emin olun ... küçük "iyileştirme", ama okunabilirlik biraz maliyet olabilir.

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

26
En az 10KB ila 100KB arasında bir tampon öneririm. Bu çok fazla değildir ve büyük miktarda veriyi muazzam bir şekilde kopyalamayı hızlandırabilir.
Aaron Digulla

6
while(len > 0)Bunun yerine şunu söylemek isteyebilirsiniz != -1, çünkü read(byte b[], int off, int len)-method kullanılırken ikincisi de 0'a dönebilir, bu da bir istisna atar @out.write
phil294 14

12
@Blauhirn: Bu, yanlıştır, çünkü InputStreamokumanın sözleşmeye göre herhangi bir sayıda 0 döndürmesi tamamen yasaldır . Ve OutputStreamsözleşmeye göre , yazma yöntemi 0 uzunluğunu kabul etmeli ve sadece lennegatif olduğunda bir istisna atmalıdır .
Christoffer Hammarström

1
Sen değiştirerek bir çizgi kaydedebilirsiniz whilea for, örneğin: ve en bölümünü init için de değişkenlerden birini koyarak for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);. =)
ɳeuroburɳ

1
@Blauhim read(), yalnızca bir programlama hatası ve sonsuza kadar döngü yapmak için aptal bir koşul olan sıfır uzunluğunu sağladıysanız sıfıra dönebilir. Ve write()gelmez değil bir sıfır uzunlukta sağlarsanız bir özel durum.
Lorne Marquis

54

Guava'ları kullanma ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);

11
Bundan sonra akışları kapatmayı unutmayın!
WonderCsabo

Benim için vazgeçilmez hale gelen Guava'yı zaten kullanıyorsanız en iyi cevap budur.
Hong

1
@Hong Files.copyMümkün olduğunca çok kullanmalısınız . Kullanım ByteStreams.copyakışlarının hem FileInputStream / FileOutputStream değildir sadece.
ZhekaKozlov

@ZhekaKozlov Bahşiş için teşekkür ederim. Benim durumumda, giriş akışı bir Android uygulamasının kaynağından (çekilebilir).
Hong

26

Basit İşlev

Yalnızca bir yazmak için bu gerekiyorsa InputStreambir karşı Fileo zaman bu basit işlevi kullanabilirsiniz:

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
Harika fonksiyon, teşekkürler. Yine de , close()çağrıları finallybloklara koymanız gerekir mi ?
Joshua Pinter

@JoshPinter Zarar vermezdi.
Jordan LaPrise

3
Muhtemelen her ikisi de son olarak bir blok içermeli ve gerçek bir uygulamada istisnaları yutmamalısınız. Ayrıca, bir yönteme iletilen bir InputStream öğesinin kapatılması bazen çağıran yöntem tarafından beklenmedik bir durumdur, bu nedenle, istedikleri davranış olup olmadığını düşünmek gerekir.
Cel Skeggs

2
IOException yeterli olduğunda neden İstisna yakalanır?
Prabhakar

18

JDK(Muhtemelen bir şey farklı zaten yapmayın) aksak üçüncü parti kütüphaneler olmadan hayır "kolay" yolu yoktur gibi kullandığı aynı kod görünüyor böylece. Aşağıdakiler doğrudan şuradan kopyalanır 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
Evet. Bu özel çağrının özel olduğunu ve bunu kendi yarar sınıfınıza kopyalamaktan başka bir seçenek olmadığından dolayı, dosyalarla değil, aynı anda 2 soketle çalışmanız mümkündür.
Dragas

17

PipedInputStreamve PipedOutputStreamyalnızca Javadoc tarafından belirtildiği gibi birden çok iş parçanız olduğunda kullanılmalıdır .

Ayrıca, giriş akışlarının ve çıkış akışlarının IOExceptions ile herhangi bir iş parçacığı kesintisi olmadığını unutmayın ... Bu nedenle, kodunuza bir kesinti ilkesi eklemeyi düşünmelisiniz:

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

Bu API'yı, büyük hacimli verileri veya dayanılmaz uzun bir süre boyunca takılan akışlardan gelen verileri kopyalamak için kullanmayı düşünüyorsanız yararlı bir ek olacaktır.


14

Spring çerçevesini kullananlar için yararlı bir StreamUtils sınıfı vardır:

StreamUtils.copy(in, out);

Yukarıdaki akışları kapatmaz. Kopyalama işleminden sonra akışların kapatılmasını istiyorsanız, bunun yerine FileCopyUtils sınıfını kullanın:

FileCopyUtils.copy(in, out);

8

Orada JDK yöntemlerle çok daha kolay bunu yapmak için hiçbir yol, ama Apocalisp daha önce belirtildiği gibi, bu fikir tek değiliz: Şunları kullanabilirsiniz IOUtils gelen Cakarta Commons IO , aynı zamanda diğer yararlı bir çok şey vardır, IMO aslında JDK'nın bir parçası olmalı ...


6

Java7 ve kaynaklarla deneme , basitleştirilmiş ve okunabilir bir sürümle birlikte gelir.

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
Döngünün içindeki yıkama, son derece üretkentir.
Lorne Marquis

5

İşte en basit for döngüsü ile yaptığım şey.

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

Commons Net'in Util sınıfını kullanın:

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

3

Bir IMHO daha minimal snippet'i (uzunluk değişkenini daha da dar kapsamlıdır):

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

Bir yan not olarak, neden daha fazla insanın bir fordöngü kullanmadığını anlamıyorum , bunun yerine whilebazıları tarafından "zayıf" stil olarak kabul edilen bir ata ve test ifadesi kullanmayı tercih ediyorum .


1
Öneriniz ilk yinelemede 0 baytlık bir yazmaya neden oluyor. Belki de en az yapmak:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
Brian de Alwis

2
@BriandeAlwis İlk yinelemenin yanlış olduğu konusunda haklısınız. Kod düzeltildi (IMHO, önerinizden daha temiz bir şekilde) - düzenlenen koda bakın. Bakım için teşekkürler.
Bohemian

3

Bu benim en iyi atışım !!

Ve kullanmayın inputStream.transferTo(...)çünkü çok genel. Arabellek belleğinizi kontrol ederseniz, kod performansınız daha iyi olacaktır.

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

Akışın boyutunu önceden bildiğimde bu (geliştirilebilir) yöntemle kullanıyorum.

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

Dosyaların çoğu 1024 bayttan büyük olduğu için büyük bir arabellek kullanmak daha iyi olduğunu düşünüyorum. Ayrıca, okunan bayt sayısını pozitif olarak kontrol etmek de iyi bir uygulamadır.

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

4
Büyük bir arabellek kullanmak gerçekten iyi bir fikirdir, ancak dosyalar çoğunlukla> 1k olduğundan, sistem çağrılarının maliyetini amorti etmektir.
Lorne Marquis

1

Ben kullanmak BufferedInputStreamve BufferedOutputStreamkoddan arabelleğe alma semantik kaldırmak için

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

'Tamponlama semantiğini koddan kaldırmak' neden iyi bir fikir?
Lorne Marquis

2
Arabelleğe alma mantığını kendim yazmıyorum, JDK'da yerleşik olanı kullanıyorum, bu genellikle yeterince iyi.
Arşimet Trajano

0

PipedInputStream ve PipedOutputStream, birini diğerine bağlayabileceğiniz için bazı yararlı olabilir.


1
Bu, tek iş parçacıklı kod için kilitlenebileceğinden iyi değildir; bu soruya bakın stackoverflow.com/questions/484119/…
Raekye

2
Bazı yararları olabilir nasıl? Zaten bir giriş akışı ve bir çıkış akışı var. Her bir yardımdan başka birini eklemek nasıl olur?
Lorne Marquis

0

Başka bir olası aday Guava I / O yardımcı programlarıdır:

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

Bunları kullanacağımı sanıyordum, çünkü Guava bir işlev için başka bir kütüphane eklemek yerine projemde son derece yararlı.


Orada copyve toByteArrayyöntemler docs.guava-libraries.googlecode.com/git-history/release/javadoc/... (guava giriş çağırır / çıkış "karakter akışları" olarak "bit akımlar" ve okuyucular / yazarlar olarak akışları)
Raekye

zaten guava kütüphaneleri kullanıyorsanız, bu iyi bir fikirdir, ancak değilse, binlerce yöntemle 'standarttan farklı olan her şeye google-of-way-of-way-of-standard-' bir mamut kütüphanesidir. Onlardan uzak
dururum

"mamut"? Çok küçük bir bağımlılık seti ve çekirdek JDK'nın çoğaltılmasını dikkatle önleyen bir API ile 2.7MB.
Adrian Baker

0

Çok okunabilir değil, etkili, bağımlılığı yok ve herhangi bir java sürümü ile çalışıyor

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

!= -1veya > 0? Bu tahminler tamamen aynı değil.
Impaler

! = -1, dosya sonu değil anlamına gelir. Bu bir yineleme değil, kılık değiştirmiş bir while-do-döngüsüdür: while ((n = inputStream.read (buffer))! = -1) {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
Bunun neden doğru cevap olduğunu açıklayabilir misiniz?
rfornal


-6

bu yöntemi kullanabilirsiniz

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){}- bu birinci sınıf
ᄀ ᄀ
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.