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

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

Итак, учитывая InputStreamin и OutputStreamout, есть ли более простой способ написать следующее?

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 29.01.2016 19:40
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
465
1
452 890
23
Перейти к ответу Данный вопрос помечен как решенный

Ответы 23

Я думаю, что это сработает, но обязательно протестируйте это ... небольшое "улучшение", но это может потребовать некоторой платы за удобочитаемость.

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

Я предлагаю буфер размером не менее 10–100 КБ. Это немного и может значительно ускорить копирование больших объемов данных.

Aaron Digulla 13.12.2008 12:24

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

Kevin Parker 09.09.2011 22:28

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

phil294 02.01.2015 17:26

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

Christoffer Hammarström 06.05.2015 19:37

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

ɲeuroburɳ 24.07.2015 20:19

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

user207421 25.02.2016 03:09

@ ChristofferHammarström нет законно, чтобы InputStream.read(byte[] b) возвращал 0 (если только вы не глупы, как сказал EJP): Если длина b равна нулю, байты не читаются и возвращается 0; в противном случае будет попытка прочитать хотя бы один байт. Если байт недоступен, потому что поток находится в конце файла, возвращается значение -1; в противном случае, по крайней мере, один байт считывается и сохраняется в b.

Andreas 16.12.2016 08:47

@Andreas: Спасибо! Я так и не понял. Я мог бы представить себе варианты использования, когда вы хотите передать буфер нулевой длины или когда это происходит по другим причинам. Так же, как я всегда пристегиваюсь ремнем безопасности, я всегда буду проверять наличие -1. Прекращение чтения и сброс оставшихся данных - это как минимум такая же ошибка, как и передача пустого буфера.

Christoffer Hammarström 16.12.2016 18:16

Другой причины нет: только буфер нулевой длины или параметр нулевой длины. Смысл вашего последнего предложения ускользает от меня.

user207421 18.06.2017 06:15

Если длина буфера равна нулю, байты не читаются и возвращается 0; в противном случае будет попытка прочитать хотя бы один байт. Если байт недоступен, потому что поток находится в конце файла, возвращается значение -1; в противном случае, по крайней мере, один байт считывается и сохраняется в буфере. - так что действительно, в этом коде возврат 0 не может произойти, поскольку размер буфера больше.

Stijn Haezebrouck 07.10.2020 12:20

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

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

Raekye 06.08.2013 20:20

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

user207421 30.10.2013 12:05

Невозможно сделать это намного проще с помощью методов JDK, но, как уже заметил Apocalisp, вы не единственный, у кого есть эта идея: вы можете использовать IOUtils из Jakarta Commons IO, у него также есть много других полезных вещей, которые IMO фактически должен быть частью JDK ...

Как упоминалось в WMR, org.apache.commons.io.IOUtils от Apache имеет метод под названием copy(InputStream,OutputStream), который делает именно то, что вы ищете.

Так что у тебя есть:

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

... в вашем коде.

Есть ли причина, по которой вы избегаете IOUtils?

Я избегаю этого для этого мобильного приложения, которое создаю, потому что оно увеличило бы размер приложения в пять раз, чтобы сэкономить жалкие 5 строк кода.

Jeremy Logan 29.08.2013 19:42

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

basZero 04.10.2013 13:51

@basZero Или с помощью блока попыток с ресурсами.

Warren Dew 17.05.2014 10:31

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

StartupGuy 23.06.2014 11:48

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

Jim Tough 19.09.2014 16:40

@ Mikezx6r Вышеупомянутый код с IOUtils загрузит весь исходящий поток в память, а затем начнет запись в исходящий поток. Это в операции с памятью не вызовет проблемы, если большие потоки?

Jigar Shah 06.04.2015 17:33

@JigarShah У вас есть ссылка на это утверждение? Когда я смотрю на исходный код, я вижу почти тот же цикл копирования, который показан OP и ответами.

Olaf Dietsche 26.05.2015 00:39

@fiXedd Вы можете использовать Maven Shade в удалить ненужные классы из финального .jar, что приведет лишь к небольшому увеличению размера файла jar

Sled 23.07.2015 18:01

Отчасти очевидно, но если у класса не слишком много зависимостей, вы также можете просто скопировать исходный код для либеральных лицензий (например, тех, которые используются как для Guava, так и для Apache). Сначала прочтите лицензию (отказ от ответственности, IANAL и т. д.).

Maarten Bodewes 10.03.2016 23:12

Размер приложения можно контролировать, если используется proguard.

Neeraj 20.06.2016 13:51

Если вы используете диспетчер зависимостей, например Maven, и избегаете IOUtils и Guava из-за размера, вы можете взглянуть на свою иерархию зависимостей. Возможно, одна из ваших зависимостей транзитивно включает одну из них без предварительного уведомления, в конце концов, они очень известны. Если это так, вы можете использовать их сами, вы все равно включаете их.

user3792852 25.07.2016 20:25

PipedInputStream и PipedOutputStream следует использовать только при наличии нескольких потоков, например отмечен Javadoc.

Также обратите внимание, что входные и выходные потоки не переносят прерывания потока в IOException ... Итак, вам следует подумать о включении политики прерывания в свой код:

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 для копирования больших объемов данных или данных из потоков, которые застревают на недопустимо долгое время.

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

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

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

В docs.guava-libraries.googlecode.com/git-history/release/java‌ doc /… есть методы copy и toByteArray (guava вызывает потоки ввода / вывода как «потоки байтов», а средства чтения / записи как «потоки символов»)

Raekye 06.08.2013 20:21

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

rupps 28.11.2014 03:08

"мамонт"? 2,7 МБ с очень небольшим набором зависимостей и API, который тщательно избегает дублирования основного JDK.

Adrian Baker 29.01.2020 05:27

Я считаю, что лучше использовать большой буфер, потому что размер большинства файлов превышает 1024 байта. Также рекомендуется проверять количество прочитанных байтов на положительное значение.

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

Использование большого буфера действительно является хорошей идеей, но не потому, что файлы в основном имеют размер> 1 КБ, это необходимо для амортизации стоимости системных вызовов.

user207421 30.10.2013 12:06

Если мы будем использовать этот код, это может вызвать проблемы с уязвимостью. Одна из лучших практик - здесь. Пожалуйста, измените соответствующим образом.

09Q71AO534 21.07.2020 15:00

Простая функция

Если вам это нужно только для записи InputStream в 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();
    }
}

Отличная функция, спасибо. Но нужно ли помещать вызовы close() в блоки finally?

Joshua Pinter 25.01.2015 02:11

@JoshPinter Это не повредит.

Jordan LaPrise 26.01.2015 07:51

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

Cel Skeggs 12.12.2015 23:40

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

Prabhakar 13.01.2017 22:13

Если мы будем использовать этот код, это может вызвать проблемы с уязвимостью. Одна из лучших практик - здесь. Пожалуйста, измените соответствующим образом.

09Q71AO534 21.07.2020 15:01

Если вы используете 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)

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

Matt Sheppard 07.10.2013 17:15

+1 все же оказался полезным в моем случае, так как у меня на выходе есть настоящий файл!

Erik Kaplun 09.04.2014 04:29

Насколько я понимаю, первый метод, который вы говорите, недоступен с этой подписью. Вам нужно добавить параметр CopyOptions.

Xtreme Biker 05.07.2014 17:11

CopyOptions является произвольным! Вы можете положить его сюда, если хотите.

user1079877 07.07.2014 06:15

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

Don Cheadle 17.12.2014 22:58

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

Joshua Pinter 25.01.2015 01:25

Забавно, но в JDK также есть Files.copy(), который принимает два потока, и это то, что все остальные функции Files.copy() используют для выполнения фактической работы по копированию. Однако он частный (поскольку на этом этапе он фактически не включает пути или файлы) и выглядит точно как код в собственном вопросе OP (плюс оператор возврата). Ни открытия, ни закрытия, только цикл копирования.

Ti Strga 09.09.2015 20:04

Для Java 9 и новее используйте InputStream.transferTo(OutputStream). stackoverflow.com/a/39440936/1441122

Stuart Marks 16.01.2019 08:40

Files.copy не имеет обратной поддержки API ниже 26 @ user1079877

mochadwi 23.02.2019 19:49

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

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

вы можете использовать этот метод

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

catch(Exception ex){} - это первоклассно

ᄂ ᄀ 25.07.2017 17:41

Используя Guava ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);

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

WonderCsabo 08.11.2014 11:40

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

Hong 29.03.2018 19:32

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

ZhekaKozlov 07.06.2018 10:38

@ZhekaKozlov Спасибо за подсказку. В моем случае входной поток поступает из ресурса приложения Android (с возможностью рисования).

Hong 07.06.2018 14:38

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

Не могли бы вы объяснить, почему это правильный ответ?

rfornal 02.04.2015 22:13

Используя 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();
}

Промывание внутри петли очень непродуктивно.

user207421 25.02.2016 03:10

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

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

В качестве примечания: я не понимаю, почему все больше людей не используют цикл for, вместо этого выбирая while с выражением «присваивай и проверяй», которое некоторые считают «плохим» стилем.

Ваше предложение вызывает 0-байтовую запись на первой итерации. Возможно, меньше всего: for(int n = 0; (n = in.read(buffer)) > 0;) { out.write(buffer, 0, n); }

Brian de Alwis 08.02.2016 06:15

@BriandeAlwis Вы правы, что первая итерация неверна. Код был исправлен (IMHO чище, чем ваше предложение) - см. Отредактированный код. Спасибо за заботу.

Bohemian 08.02.2016 07:29

Вот как я поступаю с простейшим циклом for.

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);
    }
}
Ответ принят как подходящий

Java 9

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

public long transferTo(OutputStream out) throws IOException

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

Reads all bytes from this input stream and writes the bytes to the given output stream in the order that they are read. On return, this input stream will be at end of stream. This method does not close either stream.

This method may block indefinitely reading from the input stream, or writing to the output stream. The behavior for the case where the input and/or output stream is asynchronously closed, or the thread interrupted during the transfer, is highly input and output stream specific, and therefore not specified

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

input.transferTo(output);

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

ZhekaKozlov 07.06.2018 10:47

@ZhekaKozlov К сожалению, Files.copy не обрабатывает потоки ввода / вывода любой, но он специально разработан для потоков файл.

The Impaler 09.04.2020 20:31

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

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

Dragas 13.09.2018 09:56

Для тех, кто использует Пружинный каркас, есть полезный класс StreamUtils:

StreamUtils.copy(in, out);

Вышеупомянутое не закрывает потоки. Если вы хотите, чтобы потоки закрывались после копирования, используйте вместо этого класс FileCopyUtils:

FileCopyUtils.copy(in, out);

Попробуйте Какту:

new LengthOf(new TeeInput(input, output)).value();

Подробнее здесь: http://www.yegor256.com/2017/06/22/object-orient-input-output-in-cactoos.html

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

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

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

user207421 28.01.2020 01:56

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

Archimedes Trajano 28.01.2020 03:04

Это мой лучший снимок !!

И не используйте 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
    );
}

«И не используйте inputStream.transferTo (...), потому что он слишком общий. Производительность вашего кода будет лучше, если вы будете контролировать свою буферную память». Это звучит правдоподобно, и действительно, мой собственный код изначально пытался выбрать размер буфера на основе известного размера передачи. Но я читаю, что ответ может быть более сложным, отчасти из-за размеров блока дисков и кеш-памяти процессора. Проводили ли вы какие-либо тесты в реальных условиях, чтобы подтвердить свое утверждение о том, что нестандартные размеры буфера работают лучше, чем InputStream.transferTo(OutputStream)? Если так, мне было бы интересно их увидеть. Производительность - непростая задача.

Garret Wilson 25.11.2020 18:55

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

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

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

The Impaler 09.04.2020 21:46

! = -1 означает не конец файла. Это не итерация, а замаскированный цикл while-do: while ((n = inputStream.read (buffer))! = -1) do {outputStream.write (buffer, 0, n)}

IPP Nerd 11.04.2020 02:01

Другие вопросы по теме