Простой способ записать содержимое Java InputStream в OutputStream


445

Сегодня я с удивлением обнаружил, что не могу отследить какой-либо простой способ записи содержимого объекта InputStreama OutputStreamв Java. Очевидно, что код байтового буфера не сложно написать, но я подозреваю, что мне просто не хватает чего-то, что сделало бы мою жизнь проще (и код понятнее).

Итак, учитывая an InputStream inи an 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).
Джабари

Ответы:


182

Java 9

Начиная с Java 9, InputStreamпредоставляется метод, вызываемый transferToсо следующей подписью:

public long transferTo(OutputStream out) throws IOException

Как указано в документации , transferToбудет:

Считывает все байты из этого входного потока и записывает байты в данный выходной поток в порядке их чтения. По возвращении этот входной поток будет в конце потока. Этот метод не закрывает ни один поток.

Этот метод может блокировать бесконечное чтение из входного потока или запись в выходной поток. Поведение для случая, когда входной и / или выходной поток асинхронно закрыт, или поток, прерванный во время передачи, сильно зависит от входного и выходного потока и поэтому не указано

Таким образом, чтобы записать содержимое Java InputStreamв OutputStream, вы можете написать:

input.transferTo(output);

11
Вы должны предпочитать Files.copyкак можно больше. Он реализован в собственном коде и поэтому может быть быстрее. transferToследует использовать, только если оба потока не являются FileInputStream / FileOutputStream.
Жека Козлов

@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 строк кода.
Джереми Логан

36
возможно, стоит упомянуть об этом, inи он outдолжен быть закрыт в конце кода в блоке finally
basZero

24
@basZero Или используйте блок try with resources.
Уоррен Дью

1
Или вы можете просто написать свою собственную копию (внутри, вне) оболочки ... (за меньшее время занимает ...)
MikeM

1
Если вы уже используете библиотеку Guava, Andrejs порекомендовал класс ByteStreams ниже. Подобно тому, что делает IOUtils, но избегает добавления Commons IO в ваш проект.
Джим Крутой

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
Я не думаю, что это на самом деле решает проблему, так как один конец - это путь. Хотя вы можете получить путь к файлу, насколько я знаю, вы не можете получить его для любого общего потока (например, по сети).
Мэтт Шеппард

4
CopyOptions является произвольным! Вы можете поместить это здесь, если хотите.
user1079877

4
теперь это то, что я искал! JDK на помощь, нет необходимости в другой библиотеке
Дон Чидл

7
К вашему сведению, Filesэто не доступно в Android Java 1.7. Меня задело это: stackoverflow.com/questions/24869323/…
Джошуа Пинтер

23
Забавно, что JDK также имеет a, 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
Я предлагаю буфер размером не менее 10 КБ до 100 КБ. Это не так много и может значительно ускорить копирование больших объемов данных.
Аарон Дигулла

6
вы можете сказать while(len > 0)вместо != -1, потому что последний может также вернуть 0 при использовании read(byte b[], int off, int len)-method, который выдает исключение @out.write
phil294

12
@Blauhirn: Это было бы неверно, поскольку согласно InputStreamконтракту это вполне законно для чтения, чтобы возвращать 0 любое количество раз. В соответствии с OutputStreamконтрактом, метод write должен принимать длину 0 и генерировать исключение только в случае lenотрицательного значения.
Христоффер Хаммарстрем

1
Вы можете сохранить строку, изменяя whileк forи положить одну из переменных в Близится инициализации раздел: например, for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);. =)
uroeuroburɳ

1
@Blauhim read()может возвращать ноль только в том случае, если вы указали длину нуля, что было бы ошибкой программирования и глупым условием для бесконечного цикла. И write()это не исключение , если вы предоставите нулевую длину.
маркиз Лорн

54

Использование гуавы ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);

11
Не забудьте закрыть потоки после этого!
WonderCsabo

Это лучший ответ, если вы уже используете гуаву, которая стала для меня незаменимой.
Хун

1
@Hong Вы должны использовать Files.copyкак можно больше. Используйте ByteStreams.copyтолько если оба потока не являются FileInputStream / FileOutputStream.
Жека Козлов

@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блокировались?
Джошуа Пинтер

@JoshPinter Это не повредит.
Джордан Лаприз

3
Вы, вероятно, должны оба включить блок finally и не глотать исключения в реальной реализации. Кроме того, закрытие InputStream, переданного методу, иногда является неожиданным для вызывающего метода, поэтому следует подумать о том, какое поведение они хотят.
Чел Скеггс

2
Зачем ловить исключение, когда IOException достаточно?
Прабхакар

18

Он JDKиспользует тот же код, поэтому кажется, что нет «более простого» пути без неуклюжих сторонних библиотек (которые, вероятно, в любом случае не делают ничего другого). Следующее непосредственно скопировано из 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
Айя. Позор, что этот конкретный вызов является частным, и нет другого выбора, кроме как скопировать его в свой собственный класс утилит, поскольку возможно, что вы имеете дело не с файлами, а с двумя сокетами одновременно.
Драгас

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 , у него также есть много других полезных вещей, что ИМО на самом деле должна быть частью JDK ...


6

Используя Java7 и try-with-resources , поставляется с упрощенной и читаемой версией.

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
Промывка внутри петли крайне контрпродуктивна.
маркиз Лорн

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

4

Используйте класс Util Commons Net:

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

3

ИМХО более минимальный фрагмент (который также более узко ограничивает переменную длины):

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); }
Брайан де Алвис,

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
Использование большого буфера действительно хорошая идея, но не потому, что файлы в основном> 1 Кб, это амортизация стоимости системных вызовов.
маркиз Лорн

1

Я использую BufferedInputStreamи BufferedOutputStreamудалить семантику буферизации из кода

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

Почему «удаление семантики буферизации из кода» является хорошей идеей?
маркиз Лорн

2
Это означает, что я не пишу логику буферизации самостоятельно, я использую ту, которая встроена в JDK, которая обычно достаточно хороша.
Архимед Траяно

0

PipedInputStream и PipedOutputStream могут быть полезны, так как вы можете подключить одно к другому.


1
Это не хорошо для однопоточного кода, так как это может привести к взаимоблокировке; увидеть этот вопрос stackoverflow.com/questions/484119/...
Raekye

2
Может быть какой-то пользы как? У него уже есть входной поток и выходной поток. Как будет добавляться еще одна помощь?
маркиз Лорн

0

Другой возможный кандидат - утилиты ввода / вывода Guava:

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

Я думал, что буду использовать их, так как Guava уже очень полезен в моем проекте, вместо того, чтобы добавлять еще одну библиотеку для одной функции.


Есть copyи toByteArrayметоды в docs.guava-libraries.googlecode.com/git-history/release/javadoc/… (гуава называет потоки ввода / вывода как «байтовые потоки», а читатели / писатели - как «потоки символов»)
Раекье

если вы уже используете библиотеки гуавы, это хорошая идея, но если нет, то это огромная библиотека с тысячами методов «google-way-do-do-do-do-все-иначе-к-стандартному». Я бы держался подальше от них
Рупс

«мамонты»? 2.7MB с очень небольшим набором зависимостей и API, который тщательно избегает дублирования ядра JDK.
Адриан Бейкер

0

Не очень читаемый, но эффективный, не имеет зависимостей и работает с любой версией Java

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

!= -1или > 0? Эти предикаты не совсем одинаковы.
The Impaler

! = -1 означает не конец файла. Это не итерация, а замаскированный цикл while: 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
Не могли бы вы объяснить, почему это правильный ответ?
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){}- это первоклассно
ᄀ ᄀ
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.