Простий спосіб запису вмісту Java InputStream в OutputStream


445

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

Отже, з огляду на InputStream inта 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повинен бути закритий в кінці коду в остаточному блоці
basZero

24
@basZero Або за допомогою блоку ресурсів.
Warren Dew

1
Або ви можете просто написати свою власну копію (в, з) обгортку ... (за менший час, який потрібно для ...)
MikeM

1
Якщо ви вже використовуєте бібліотеку Guava, Андрейс рекомендував нижче клас ByteStreams. Подібно до того, що робить IOUtils, але уникає додавання I Commons в проект.
Джим Крутий

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
FYI, FilesНЕ доступний в Android 1.7 на Android . Мене вразило це: stackoverflow.com/questions/24869323/…
Джошуа Пінтер

23
Кумедно, JDK також має a, Files.copy()який приймає два потоки, і це те, що всі інші Files.copy()функції вперед для того, щоб виконати фактичну роботу з копіювання. Однак він є приватним (оскільки він фактично не включає Шляхи чи Файли на цьому етапі), і виглядає точно як код у власному питанні ОП (плюс зворотний вираз). Ні відкриття, ні закриття, лише цикл копіювання.
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договору, метод запису повинен приймати довжину 0 і повинен викидати виключення лише тоді, коли lenце негативно.
Крістофер Хаммарстрем

1
Ви можете зберегти рядок, змінюючи whileдо forі покласти одну з змінних в Наближається ініціалізації розділ: наприклад, for (int n ; (n = in.read(buf)) != -1 ;) out.write(buf, 0, n);. =)
ɲeuroburɳ

1
@Blauhim read()може повернути нуль лише у тому випадку, якщо ви вказали довжину нуля, що було б помилкою програмування та дурною умовою, щоб навіювати цикл назавжди. І write()це НЕ виняток , якщо ви надасте нульову довжину.
Маркіз Лорн

54

Використання гуави ByteStreams.copy():

ByteStreams.copy(inputStream, outputStream);

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

Це найкраща відповідь, якщо ви вже використовуєте Guava, який став для мене незамінним.
Гонг

1
@Hong Вам слід використовувати Files.copyякомога більше. Використовуйте ByteStreams.copyлише якщо обидва потоки не є FileInputStream / FileOutputStream.
ЖекаКозлов

@ZhekaKozlov Дякую за пораду. У моєму випадку вхідний потік відбувається з ресурсу програми Android (драйвовий).
Гонг

26

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

Якщо вам це потрібно лише для написання 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();
    }
}

4
Відмінна функція, спасибі Чи потрібно вам розміщувати close()дзвінки в finallyблоки?
Джошуа Пінтер

@JoshPinter Це не завадило б.
Йордан LaPrise

3
Вам, мабуть, слід обоє включати остаточний блок і не проковтувати винятки в реальній реалізації. Крім того, закриття 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
Ага. Соромно, що цей конкретний виклик є приватним, і немає іншого варіанту, крім того, щоб скопіювати його у свій власний клас комунальних служб, оскільки можливо, ви не маєте справу з файлами, а радше 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, але як вже зазначав Апокалісп, ви не єдина з цією ідеєю: ви можете використовувати IOUtils від IO Jakarta Commons , в ньому також є багато інших корисних речей, що IMO насправді повинен бути частиною JDK ...


6

Використовуючи 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();
}

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

Більш мінімальний фрагмент IMHO (який також більш вузько охоплює змінну довжини):

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 чистішим чином, ніж ваша пропозиція) - див. Відредагований код. Thx для турботи.
богем

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/… (guava називає вхідні / вихідні потоки "потоками байтів", а читачі / автори - "потоками байт")
Raekye

якщо ви вже використовуєте бібліотеки guava, це гарна ідея, але якщо ні, то це бібліотека мамонтів з тисячами методів "google-way-of-doing-vse-different-to-the-standard". Я б тримався подалі від них
рвап

"мамонт"? 2,7 МБ із дуже невеликим набором залежностей та 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 означає не кінець файлу. Це не ітерація, а цикл "time-do-do" в маскуванні: while ((n = inputStream.read (буфер))! = -1) do {outputStream.write (буфер, 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.