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

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

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?


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)

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

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

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

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

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.


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

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 ...


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

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

4

Użyj klasy Util Commons Net:

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

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.


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

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

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

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ą.


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.


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

!= -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;
}

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

6
catch(Exception ex){}- to jest na najwyższym poziomie
ᄂ ᄀ
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.