Я был удивлен, обнаружив сегодня, что не могу найти какой-либо простой способ записать содержимое 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);
}




Я думаю, что это сработает, но обязательно протестируйте это ... небольшое "улучшение", но это может потребовать некоторой платы за удобочитаемость.
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
Я предлагаю буфер размером не менее 10–100 КБ. Это немного и может значительно ускорить копирование больших объемов данных.
Это помогло мне при написании файла. Я не понимал, что мне нужно получить фактическую длину, считанную из входного потока, и использовать ее для вывода в файл.
вы можете сказать while(len > 0) вместо != -1, потому что последний также может возвращать 0 при использовании метода read(byte b[], int off, int len), который генерирует исключение @ out.write
@Blauhirn: Это было бы неправильно, поскольку согласно контракту InputStream это совершенно законно для чтения для возврата 0 любое количество раз. А согласно контракту OutputStream метод записи должен принимать длину 0 и генерировать исключение только в том случае, если значение len отрицательное.
Вы можете сохранить строку, изменив while на for и поместив одну из переменных в раздел инициализации for: например, for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);. знак равно
@Blauhim read() может возвращать ноль только в том случае, если вы указали нулевую длину, что было бы ошибкой программирования и глупым условием для бесконечного цикла. И write() вызывает исключение нет, если вы указываете нулевую длину.
@ ChristofferHammarström нет законно, чтобы InputStream.read(byte[] b) возвращал 0 (если только вы не глупы, как сказал EJP): Если длина b равна нулю, байты не читаются и возвращается 0; в противном случае будет попытка прочитать хотя бы один байт. Если байт недоступен, потому что поток находится в конце файла, возвращается значение -1; в противном случае, по крайней мере, один байт считывается и сохраняется в b.
@Andreas: Спасибо! Я так и не понял. Я мог бы представить себе варианты использования, когда вы хотите передать буфер нулевой длины или когда это происходит по другим причинам. Так же, как я всегда пристегиваюсь ремнем безопасности, я всегда буду проверять наличие -1. Прекращение чтения и сброс оставшихся данных - это как минимум такая же ошибка, как и передача пустого буфера.
Другой причины нет: только буфер нулевой длины или параметр нулевой длины. Смысл вашего последнего предложения ускользает от меня.
Если длина буфера равна нулю, байты не читаются и возвращается 0; в противном случае будет попытка прочитать хотя бы один байт. Если байт недоступен, потому что поток находится в конце файла, возвращается значение -1; в противном случае, по крайней мере, один байт считывается и сохраняется в буфере. - так что действительно, в этом коде возврат 0 не может произойти, поскольку размер буфера больше.
PipedInputStream и PipedOutputStream могут быть полезны, поскольку вы можете подключать один к другому.
Это не подходит для однопоточного кода, поскольку может привести к взаимной блокировке; посмотреть этот вопрос stackoverflow.com/questions/484119/…
Может быть полезно как? У него уже есть поток ввода и поток вывода. Каким образом добавление еще одного каждого из них поможет?
Невозможно сделать это намного проще с помощью методов 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 строк кода.
возможно, стоит упомянуть, что in и out должны быть закрыты в конце кода в блоке finally
@basZero Или с помощью блока попыток с ресурсами.
Или вы можете просто написать свою собственную копию (входящую, выходящую) обертку ... (за меньшее время, чтобы ...)
Если вы уже используете библиотеку Guava, Андрей порекомендовал класс ByteStreams ниже. Подобно тому, что делает IOUtils, но избегает добавления Commons IO в ваш проект.
@ Mikezx6r Вышеупомянутый код с IOUtils загрузит весь исходящий поток в память, а затем начнет запись в исходящий поток. Это в операции с памятью не вызовет проблемы, если большие потоки?
@JigarShah У вас есть ссылка на это утверждение? Когда я смотрю на исходный код, я вижу почти тот же цикл копирования, который показан OP и ответами.
@fiXedd Вы можете использовать Maven Shade в удалить ненужные классы из финального .jar, что приведет лишь к небольшому увеличению размера файла jar
Отчасти очевидно, но если у класса не слишком много зависимостей, вы также можете просто скопировать исходный код для либеральных лицензий (например, тех, которые используются как для Guava, так и для Apache). Сначала прочтите лицензию (отказ от ответственности, IANAL и т. д.).
Размер приложения можно контролировать, если используется proguard.
Если вы используете диспетчер зависимостей, например Maven, и избегаете IOUtils и Guava из-за размера, вы можете взглянуть на свою иерархию зависимостей. Возможно, одна из ваших зависимостей транзитивно включает одну из них без предварительного уведомления, в конце концов, они очень известны. Если это так, вы можете использовать их сами, вы все равно включаете их.
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 вызывает потоки ввода / вывода как «потоки байтов», а средства чтения / записи как «потоки символов»)
если вы уже используете библиотеки guava, это хорошая идея, но если нет, то это гигантская библиотека с тысячами методов «google-way-of-do-all-different-to-the-standard». Я бы держался от них подальше
"мамонт"? 2,7 МБ с очень небольшим набором зависимостей и API, который тщательно избегает дублирования основного JDK.
Я считаю, что лучше использовать большой буфер, потому что размер большинства файлов превышает 1024 байта. Также рекомендуется проверять количество прочитанных байтов на положительное значение.
byte[] buffer = new byte[4096];
int n;
while ((n = in.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
out.close();
Использование большого буфера действительно является хорошей идеей, но не потому, что файлы в основном имеют размер> 1 КБ, это необходимо для амортизации стоимости системных вызовов.
Если мы будем использовать этот код, это может вызвать проблемы с уязвимостью. Одна из лучших практик - здесь. Пожалуйста, измените соответствующим образом.
Если вам это нужно только для записи 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?
@JoshPinter Это не повредит.
Вероятно, вам следует включить блок finally и не проглатывать исключения в реальной реализации. Кроме того, закрытие InputStream, переданного методу, иногда является неожиданным для вызывающего метода, поэтому следует подумать, является ли это поведение, которое они хотят.
Зачем ловить исключение, когда достаточно IOException?
Если мы будем использовать этот код, это может вызвать проблемы с уязвимостью. Одна из лучших практик - здесь. Пожалуйста, измените соответствующим образом.
Если вы используете 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)
Я не думаю, что это действительно решает проблему, поскольку один конец - это путь. Хотя вы можете получить путь к файлу, насколько мне известно, вы не можете получить его ни для одного общего потока (например, по сети).
+1 все же оказался полезным в моем случае, так как у меня на выходе есть настоящий файл!
Насколько я понимаю, первый метод, который вы говорите, недоступен с этой подписью. Вам нужно добавить параметр CopyOptions.
CopyOptions является произвольным! Вы можете положить его сюда, если хотите.
теперь это - это то, что я искал! JDK приходит на помощь, нет необходимости в другой библиотеке
К вашему сведению, Files НЕ доступен в Java 1.7 от Android. Меня ужалило это: stackoverflow.com/questions/24869323/…
Забавно, но в JDK также есть Files.copy(), который принимает два потока, и это то, что все остальные функции Files.copy() используют для выполнения фактической работы по копированию. Однако он частный (поскольку на этом этапе он фактически не включает пути или файлы) и выглядит точно как код в собственном вопросе OP (плюс оператор возврата). Ни открытия, ни закрытия, только цикл копирования.
Для Java 9 и новее используйте InputStream.transferTo(OutputStream). stackoverflow.com/a/39440936/1441122
Files.copy не имеет обратной поддержки API ниже 26 @ user1079877
Используйте класс 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){} - это первоклассно
Используя Guava ByteStreams.copy():
ByteStreams.copy(inputStream, outputStream);
Не забудьте после этого закрыть потоки!
Это лучший ответ, если вы уже используете Guava, который стал для меня незаменимым.
@Hong Вы должны использовать Files.copy как можно чаще. Используйте ByteStreams.copy, только если оба потока не являются FileInputStream / FileOutputStream.
@ZhekaKozlov Спасибо за подсказку. В моем случае входной поток поступает из ресурса приложения Android (с возможностью рисования).
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;
}
Не могли бы вы объяснить, почему это правильный ответ?
Используя 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();
}
Промывание внутри петли очень непродуктивно.
ИМХО более минимальный фрагмент (который также более узко охватывает переменную длины):
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); }
@BriandeAlwis Вы правы, что первая итерация неверна. Код был исправлен (IMHO чище, чем ваше предложение) - см. Отредактированный код. Спасибо за заботу.
Вот как я поступаю с простейшим циклом 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, 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 К сожалению, Files.copy не обрабатывает потоки ввода / вывода любой, но он специально разработан для потоков файл.
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;
}
Да. Жаль, что этот конкретный вызов является частным, и нет другого выхода, кроме как скопировать его в свой собственный класс утилит, поскольку возможно, что вы имеете дело не с файлами, а с двумя сокетами одновременно.
Для тех, кто использует Пружинный каркас, есть полезный класс 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);
}
}
Почему «удалить семантику буферизации из кода» - хорошая идея?
Это означает, что я не пишу логику буферизации сам, я использую ту, что встроена в JDK, что обычно достаточно хорошо.
Это мой лучший снимок !!
И не используйте 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)? Если так, мне было бы интересно их увидеть. Производительность - непростая задача.
Не очень читаемый, но эффективный, не имеет зависимостей и работает с любой версией java.
byte[] buffer = new byte[1024];
for (int n; (n = inputStream.read(buffer)) != -1; outputStream.write(buffer, 0, n));
!= -1 или > 0? Эти предикаты не совсем то же самое.
! = -1 означает не конец файла. Это не итерация, а замаскированный цикл while-do: while ((n = inputStream.read (buffer))! = -1) do {outputStream.write (buffer, 0, n)}
Вы упомянули в комментарии, что это для мобильного приложения. Это родной Android? Если да, дайте мне знать, и я опубликую еще один ответ (это можно сделать одной строкой кода в Android).