Łatwy sposób zapisywania zawartości Java InputStream do OutputStream

445

Byłem zaskoczony, gdy dowiedziałem się dzisiaj, że nie mogę wyśledzić żadnego prostego sposobu zapisania zawartości InputStreaman OutputStreamw Javie. Oczywiście bajtowy kod bufora nie jest trudny do napisania, ale podejrzewam, że po prostu brakuje mi czegoś, co ułatwiłoby mi życie (i kod byłby wyraźniejszy).

Tak, podać InputStream ini OutputStream outczy istnieje prostszy sposób napisać co następuje?

byte[] buffer = new byte[1024];
int len = in.read(buffer);
while (len != -1) {
    out.write(buffer, 0, len);
    len = in.read(buffer);
}
Matt Sheppard
źródło
W komentarzu wspomniałeś, że dotyczy to aplikacji mobilnej. Czy to natywny Android? Jeśli tak, daj mi znać, a ja opublikuję inną odpowiedź (można to zrobić, to jedna linia kodu w Androidzie).
Jabari

Odpowiedzi:

182

Java 9

Od wersji Java 9 InputStreamudostępnia metodę wywoływaną transferToz następującym podpisem:

public long transferTo(OutputStream out) throws IOException

Jak dokumentacji stanów, transferTobędzie:

Odczytuje wszystkie bajty z tego strumienia wejściowego i zapisuje bajty do podanego strumienia wyjściowego w kolejności, w jakiej zostały odczytane. Po powrocie ten strumień wejściowy będzie na końcu strumienia. Ta metoda nie zamyka żadnego strumienia.

Ta metoda może blokować nieokreślony odczyt ze strumienia wejściowego lub zapis do strumienia wyjściowego. Zachowanie w przypadku, gdy strumień wejściowy i / lub wyjściowy jest asynchronicznie zamknięty lub wątek przerwany podczas przesyłania, jest wysoce specyficzny dla strumienia wejściowego i wyjściowego, a zatem nie jest określony

Aby więc zapisać zawartość Javy InputStreamna an OutputStream, możesz napisać:

input.transferTo(output);
Ali Dehghani
źródło
11
Powinieneś preferować Files.copyjak najwięcej. Jest zaimplementowany w kodzie natywnym i dlatego może być szybszy. transferTonależy używać tylko wtedy, gdy oba strumienie nie są FileInputStream / FileOutputStream.
ZhekaKozlov
@ZhekaKozlov Niestety Files.copynie obsługuje żadnych strumieni wejściowych / wyjściowych, ale jest specjalnie zaprojektowany dla strumieni plików .
The Impaler
396

Jak wspomniano WMR, org.apache.commons.io.IOUtilsApache ma metodę o nazwie, copy(InputStream,OutputStream)która robi dokładnie to, czego szukasz.

Więc masz:

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

... w twoim kodzie.

Czy istnieje powód, dla którego unikasz IOUtils?

Mikezx6r
źródło
170
Unikam tego dla tej aplikacji mobilnej, którą buduję, ponieważ czterokrotnie zwiększyłaby rozmiar aplikacji, aby zaoszczędzić zaledwie 5 wierszy kodu.
Jeremy Logan
36
może warto o tym wspomnieć ini outmusi zostać zamknięty na końcu kodu w końcu bloku
basZero,
24
@basZero Lub używając try z blokiem zasobów.
Warren Dew
1
Lub możesz po prostu napisać własną kopię (wewnątrz, na zewnątrz) otoki ... (w krótszym czasie zajmuje ...)
MikeM
1
Jeśli już korzystasz z biblioteki Guava, Andrejs polecił klasę ByteStreams poniżej. Podobne do tego, co robi IOUtils, ale unika dodawania Commons IO do twojego projektu.
Jim Tough,
328

Jeśli używasz Java 7, najlepszym rozwiązaniem jest Pliki (w standardowej bibliotece):

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

Edycja: Oczywiście jest to przydatne, gdy tworzysz jeden z InputStream lub OutputStream z pliku. Użyj, file.toPath()aby uzyskać ścieżkę z pliku.

Aby zapisać do istniejącego pliku (np. Utworzonego za pomocą File.createTempFile()), musisz przekazać REPLACE_EXISTINGopcję kopiowania (w przeciwnym razie FileAlreadyExistsExceptionzostanie wyrzucone):

Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING)
użytkownik1079877
źródło
26
Nie sądzę, że to faktycznie rozwiązuje problem, ponieważ jeden koniec jest ścieżką. Chociaż można uzyskać ścieżkę do pliku, o ile wiem, nie można jej uzyskać dla żadnego ogólnego strumienia (np. Jednego przez sieć).
Matt Sheppard
4
CopyOptions jest arbitralne! Możesz go tutaj umieścić, jeśli chcesz.
user1079877,
4
teraz tego szukałem! JDK na ratunek, nie potrzeba kolejnej biblioteki
Don Cheadle
7
Do Twojej wiadomości FilesNIE jest dostępny w Javie 1.7 w Androidzie . Uderzyło mnie to: stackoverflow.com/questions/24869323/...
Joshua Pinter
23
Zabawne jest to, że JDK ma również Files.copy()dwa strumienie i jest tym, do czego Files.copy()służą wszystkie pozostałe funkcje w celu wykonania rzeczywistej pracy kopiowania. Jest jednak prywatny (ponieważ na tym etapie tak naprawdę nie obejmuje ścieżek ani plików) i wygląda dokładnie tak , jak kod we własnym pytaniu OP (plus instrukcja return). Bez otwierania, bez zamykania, tylko pętla kopiująca.
Ti Strga,
102

Myślę, że to zadziała, ale upewnij się, że to przetestujesz ... drobna „poprawa”, ale może to być trochę kosztowne pod względem czytelności.

byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
    out.write(buffer, 0, len);
}
Mike Stone
źródło
26
Sugeruję bufor o wielkości co najmniej 10 KB do 100 KB. To niewiele i może znacznie przyspieszyć kopiowanie dużych ilości danych.
Aaron Digulla,
6
możesz chcieć powiedzieć while(len > 0)zamiast != -1, ponieważ ten ostatni może również zwrócić 0, gdy używasz read(byte b[], int off, int len)-method, która zgłasza wyjątek @out.write
phil294
12
@Blauhirn: To byłoby niepoprawne, ponieważ zgodnie z InputStreamumową read read zwraca 0 dowolną liczbę razy. Zgodnie z OutputStreamumową metoda zapisu musi przyjmować długość 0 i powinna zgłaszać wyjątek tylko wtedy, gdy lenjest ujemna.
Christoffer Hammarström
1
Możesz zapisać linię, zmieniając na whilea fori umieszczając jedną ze zmiennych w sekcji inicjującej for: np for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);. =)
ɲeuroburɳ
1
@ Blauhim read()może zwrócić zero tylko wtedy, gdy podasz długość zero, co byłoby błędem programowania i głupim warunkiem ciągłego zapętlania . I write()nie nie wyjątek jeśli podasz długość zerową.
Markiz Lorne
54

Korzystanie z Guawy ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);
Andrejs
źródło
11
Nie zapomnij potem zamknąć strumieni!
WonderCsabo
To najlepsza odpowiedź, jeśli już używasz Guawy, która stała się dla mnie niezbędna.
Hong
1
@Hong Powinieneś używać Files.copyjak najwięcej. Używaj ByteStreams.copytylko wtedy, gdy oba strumienie nie są FileInputStream / FileOutputStream.
ZhekaKozlov
@ZhekaKozlov Dziękujemy za wskazówkę. W moim przypadku strumień wejściowy pochodzi z zasobu aplikacji na Androida (do pobrania).
Hong
26

Prosta funkcja

Jeśli tylko trzeba to na pisanie InputStreamDo Filenastępnie można użyć tej prostej funkcji:

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
źródło
4
Świetna funkcja, dzięki. Czy musiałbyś jednak umieszczać close()połączenia w finallyblokach?
Joshua Pinter
@JoshPinter Nie zaszkodzi.
Jordan LaPrise
3
Prawdopodobnie powinieneś objąć wreszcie blok i nie połykać wyjątków w rzeczywistej implementacji. Ponadto zamknięcie InputStream przekazanego do metody jest czasem nieoczekiwane przez metodę wywołującą, dlatego należy rozważyć, czy jest to zachowanie, którego chcą.
Cel Skeggs,
2
Po co łapać wyjątek, gdy wystarcza wyjątek IOException?
Prabhakar
18

Do JDKzastosowania tego samego kodu, więc wydaje się, że nie ma „łatwiejszy” sposób bez przylegający bibliotek strony trzeciej (która prawdopodobnie nic nie różni się w każdym razie zrobić). Poniższe dane są kopiowane bezpośrednio z 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
źródło
2
Zawsze. Szkoda, że ​​to szczególne połączenie jest prywatne i nie ma innej opcji, jak skopiować je do własnej klasy narzędzi, ponieważ możliwe jest, że nie masz do czynienia z plikami, a raczej 2 gniazdami naraz.
Dragas,
17

PipedInputStreami PipedOutputStreampowinien być używany tylko wtedy, gdy masz wiele wątków, jak zauważył Javadoc .

Zauważ też, że strumienie wejściowe i wyjściowe nie zawijają żadnych przerw wątków za pomocą IOExceptions ... Więc powinieneś rozważyć włączenie polityki przerwania do swojego kodu:

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

Byłby to użyteczny dodatek, jeśli spodziewasz się użyć tego interfejsu API do kopiowania dużych ilości danych lub danych ze strumieni, które utknęły na niedopuszczalnie długim czasie.

Dilum Ranatunga
źródło
14

Dla tych, którzy korzystają z frameworka Spring, przydatna jest klasa StreamUtils :

StreamUtils.copy(in, out);

Powyższe nie zamyka strumieni. Jeśli chcesz, aby strumienie były zamknięte po kopiowaniu, użyj klasy FileCopyUtils :

FileCopyUtils.copy(in, out);
holmis83
źródło
8

Nie ma sposobu, aby zrobić to o wiele łatwiej dzięki metodom JDK, ale jak już zauważył Apocalisp, nie jesteś jedyny z tym pomysłem: możesz użyć IOUtils z Jakarta Commons IO , ma również wiele innych przydatnych rzeczy, że IMO powinna faktycznie być częścią JDK ...

WMR
źródło
6

Korzystanie z Java7 i try-with-resources zawiera uproszczoną i czytelną wersję.

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
źródło
3
Płukanie w pętli jest bardzo szkodliwe.
Markiz Lorne
5

Oto, jak sobie radzę z najprostszą pętlą.

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
źródło
4

Użyj klasy Util Commons Net:

import org.apache.commons.net.io.Util;
...
Util.copyStream(in, out);
DejanLekic
źródło
3

Bardziej minimalny fragment kodu IMHO (który również zawęża zakres zmiennej długości):

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

Na marginesie, nie rozumiem, dlaczego więcej osób nie używa forpętli, zamiast tego wybiera whilewyrażenie z przypisywaniem i testowaniem, które przez niektórych jest uważane za „zły” styl.

Czech
źródło
1
Twoja sugestia powoduje zapis 0 bajtów przy pierwszej iteracji. Być może przynajmniej:for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }
Brian de Alwis
2
@BriandeAlwis Masz rację, że pierwsza iteracja jest nieprawidłowa. Kod został naprawiony (IMHO w czystszy sposób niż twoja sugestia) - patrz edytowany kod. Dzięki za opiekę.
Czeski
3

To mój najlepszy strzał !!

I nie używaj, inputStream.transferTo(...)ponieważ jest zbyt ogólny. Wydajność kodu będzie lepsza, jeśli kontrolujesz pamięć bufora.

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

Używam go z tą (poprawialną) metodą, gdy wiem z góry rozmiar strumienia.

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
źródło
2

Myślę, że lepiej jest użyć dużego bufora, ponieważ większość plików ma ponad 1024 bajty. Dobrą praktyką jest również sprawdzanie liczby odczytanych bajtów, aby były dodatnie.

byte[] buffer = new byte[4096];
int n;
while ((n = in.read(buffer)) > 0) {
    out.write(buffer, 0, n);
}
out.close();
Alexander Volkov
źródło
4
Korzystanie z dużego bufora jest wprawdzie dobrym pomysłem, ale nie dlatego, że pliki są w większości> 1k, ma to na celu amortyzację kosztów wywołań systemowych.
Markiz Lorne
1

Używam BufferedInputStreami BufferedOutputStreamdo usunięcia semantyki buforowania z kodu

try (OutputStream out = new BufferedOutputStream(...);
     InputStream in   = new BufferedInputStream(...))) {
  int ch;
  while ((ch = in.read()) != -1) {
    out.write(ch);
  }
}
Archimedes Trajano
źródło
Dlaczego „usunięcie semantyki buforowania z kodu” jest dobrym pomysłem?
Markiz Lorne
2
Oznacza to, że nie piszę logiki buforowania, używam tej wbudowanej w JDK, która zwykle jest wystarczająco dobra.
Archimedes Trajano
0

PipedInputStream i PipedOutputStream mogą się przydać, ponieważ możesz łączyć się ze sobą.

Arktronic
źródło
1
Nie jest to dobre w przypadku kodu jednowątkowego, ponieważ mogłoby to spowodować zakleszczenie; zobacz to pytanie stackoverflow.com/questions/484119/…
Raekye
2
Może się przydać, jak? Ma już strumień wejściowy i strumień wyjściowy. W jaki sposób dodanie kolejnej z nich dokładnie pomoże?
Markiz Lorne
0

Innym możliwym kandydatem są narzędzia we / wy Guava:

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

Myślałem, że skorzystam z nich, ponieważ Guava jest już niezwykle przydatny w moim projekcie, zamiast dodawać kolejną bibliotekę dla jednej funkcji.

Andrew Mao
źródło
Istnieją copyi toByteArraymetody w docs.guava-libraries.googlecode.com/git-history/release/javadoc/… (guava wywołuje strumienie wejściowe / wyjściowe jako „strumienie bajtów”, a czytelników / pisarzy jako „strumienie
znaków
jeśli już korzystasz z bibliotek guava, to dobry pomysł, ale jeśli nie, są to gigantyczne biblioteki z tysiącami metod „google-way-of-robić-wszystko-różne-od-standardu”.
Trzymałbym
"mamut"? 2,7 MB z bardzo małym zestawem zależności i interfejsem API, który ostrożnie unika powielania podstawowego JDK.
Adrian Baker
0

Niezbyt czytelny, ale skuteczny, nie ma zależności i działa z każdą wersją Java

byte[] buffer = new byte[1024];
for (int n; (n = inputStream.read(buffer)) != -1; outputStream.write(buffer, 0, n));
IPP Nerd
źródło
!= -1czy > 0? Te prognozy nie są takie same.
The Impaler
! = -1 oznacza non-end-of-file. To nie jest iteracja, ale ukryta pętla while-do:: while ((n = inputStream.read (bufor))! = -1) do {outputStream.write (bufor, 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
źródło
4
Czy możesz wyjaśnić, dlaczego jest to właściwa odpowiedź?
rfornal
-6

możesz użyć tej metody

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){}
 }
Pranaw
źródło
6
catch(Exception ex){}- to jest na najwyższym poziomie
ᄂ ᄀ