Java NIO FileChannel порівняно з продуктивністю / корисністю FileOutputstream


169

Я намагаюся з'ясувати, чи є різниця у продуктивності (або переваги), коли ми використовуємо nio FileChannelпорівняно з нормальним FileInputStream/FileOuputStreamдля читання та запису файлів у файлову систему. Я помітив, що на моїй машині обидва працюють на одному рівні, також багато разів FileChannelшлях проходить повільніше. Чи можу я дізнатися більше деталей, порівнюючи ці два методи. Ось код, який я використав, файл, з яким я тестую, є навколо 350MB. Чи є хорошим варіантом використання класів на основі NIO для вводу / виводу файлів, якщо я не дивлюся на випадковий доступ чи інші такі розширені функції?

package trialjavaprograms;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class JavaNIOTest {
    public static void main(String[] args) throws Exception {
        useNormalIO();
        useFileChannel();
    }

    private static void useNormalIO() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        InputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        byte[] buf = new byte[64 * 1024];
        int len = 0;
        while((len = is.read(buf)) != -1) {
            fos.write(buf, 0, len);
        }
        fos.flush();
        fos.close();
        is.close();
        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }

    private static void useFileChannel() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        FileInputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        FileChannel f = is.getChannel();
        FileChannel f2 = fos.getChannel();

        ByteBuffer buf = ByteBuffer.allocateDirect(64 * 1024);
        long len = 0;
        while((len = f.read(buf)) != -1) {
            buf.flip();
            f2.write(buf);
            buf.clear();
        }

        f2.close();
        f.close();

        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }
}

5
transferTo/ transferFromбуло б більш звичайним для копіювання файлів. Яка б техніка не повинна робити ваш жорсткий диск ні швидшим, ні повільнішим, хоча, мабуть, може виникнути проблема, якщо він читатиме невеликі шматки за раз і змушує голову витрачати непотрібну кількість часу на пошуки.
Том Хотін - тайклін

1
(Ви не згадуєте, яку ОС ви використовуєте, або який постачальник та версію JRE.)
Том Хоутін - Tackline

На жаль, я використовую FC10 з Sun JDK6.
Кешав

Відповіді:


202

Мій досвід роботи з більшими розмірами файлів - java.nioце швидше, ніж java.io. Суцільно швидше. Як і в діапазоні> 250%. З цього приводу я усуваю очевидні вузькі місця, за якими я думаю, що може постраждати ваш мікро-орієнтир. Потенційні напрямки дослідження:

Розмір буфера. Алгоритм, який у вас є, є

  • копія з диска в буфер
  • копія з буфера на диск

Мій власний досвід полягає в тому, що цей розмір буфера дозрів для настройки. Я влаштувався на 4 КБ для однієї частини моєї програми, а 256 Кб - для іншої. Я підозрюю, що ваш код страждає від такого великого буфера. Запустіть кілька орієнтирів з буферами 1 КБ, 2 КБ, 4 КБ, 8 КБ, 16 КБ, 32 КБ і 64 КБ, щоб довести це собі.

Не виконуйте тести Java, які читають і записують на один і той же диск.

Якщо ви це зробите, то ви дійсно орієнтуєте диск, а не Java. Я б також запропонував, що якщо ваш процесор не зайнятий, ви, мабуть, відчуваєте ще якесь вузьке місце.

Не використовуйте буфер, якщо цього не потрібно.

Навіщо копіювати в пам'ять, якщо ваша мета - інший диск або NIC? Що стосується великих файлів, то час затримки не тривіальний.

Як і інші сказали, використовуйте FileChannel.transferTo()або FileChannel.transferFrom(). Ключовою перевагою тут є те, що JVM використовує доступ ОС до DMA ( Direct Memory Access ), якщо такий є. (Це залежить від реалізації, але сучасні версії Sun та IBM на центральних процесорах загального призначення корисні.) Що трапляється - дані надходять прямо в / з диска, на шину, а потім до місця призначення ... минаючи будь-яку схему через ОЗУ або процесор.

Веб-додаток, над яким я проводив свої дні та ночі, дуже важкий. Я також робив мікро-орієнтири та реальних орієнтирів. І результати на моєму блозі, перегляньте:

Використовуйте виробничі дані та середовища

Мікро-орієнтири схильні до викривлення. Якщо ви можете, докладіть зусиль, щоб зібрати дані саме з того, що ви плануєте зробити, з навантаженням, яке ви очікуєте, на обладнання, яке ви очікуєте.

Мої орієнтири є надійними та надійними, оскільки вони мали місце у виробничій системі, бичачій системі, системі під навантаженням, зібраній у колоди. Не моє ноутбук 7200 RPM 2.5 "SATA диск, поки я інтенсивно спостерігав, як JVM працює на моєму жорсткому диску.

На що ти біжиш? Це важливо.


@Сту Томпсон - Дякую за ваш пост. Я натрапив на вашу відповідь, оскільки досліджую ту саму тему. Я намагаюся зрозуміти вдосконалення ОС, які nio піддає програмістам java. Кілька з них - DMA і файли з картою пам'яті. Чи стикалися ви з такими вдосконаленнями? PS - Ваші посилання на блог порушені.
Енді Дуфресне

@AndyDufresne мій блог наразі не працює, буде запущено пізніше цього тижня - у процесі його переміщення.
Стю Томпсон


1
Як щодо просто копіювання файлів з одного каталогу в інший? (Кожен різний диск)
Deckard


38

Якщо ви хочете порівняти ефективність копіювання файлів, то для тестування каналів слід зробити це:

final FileInputStream inputStream = new FileInputStream(src);
final FileOutputStream outputStream = new FileOutputStream(dest);
final FileChannel inChannel = inputStream.getChannel();
final FileChannel outChannel = outputStream.getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
inputStream.close();
outputStream.close();

Це не буде повільніше, ніж буферизація себе з одного каналу на інший, і потенційно буде значно швидше. Відповідно до Javadocs:

Багато операційних систем можуть передавати байти безпосередньо з кешу файлової системи на цільовий канал, фактично не копіюючи їх.


7

На основі моїх тестів (Win7 64bit, 6 Гб оперативної пам’яті, Java6) передача NIO швидка лише з невеликими файлами і стає дуже повільною для великих файлів. Перевірка даних NIO завжди перевершує стандартний IO.

  • Копіювання 1000x2MB

    1. NIO (трансфер від) ~ 2300 мс
    2. NIO (прямий фліп datababuffer 5000b) ~ 3500 мс
    3. Стандартний IO (буфер 5000b) ~ 6000 мс
  • Копіювання 100х20 Мб

    1. NIO (прямий фліп datababuffer 5000b) ~ 4000 мс
    2. NIO (трансфер від) ~ 5000 мс
    3. Стандартний IO (буфер 5000b) ~ 6500 мс
  • Копіювання 1х1000 Мб

    1. NIO (прямий фліп datababuffer 5000b) ~ 4500
    2. Стандартний IO (буфер 5000b) ~ 7000 мс
    3. NIO (трансфер від) ~ 8000 мс

Метод transferTo () працює на фрагменти файлу; не розглядався як метод копіювання файлів високого рівня: Як скопіювати великий файл у Windows XP?


6

Відповідь на "корисність" частини питання:

Один з досить тонких проблем використання FileChannelнад FileOutputStreamтим, що виконання будь-якої з його блокуючих операцій (наприклад, read()або write()) з потоку, який перебуває в перерваному стані, призведе до того, що канал буде різко закриватися java.nio.channels.ClosedByInterruptException.

Тепер це може бути хорошою справою, якщо те, що FileChannelбуло використано, є частиною основної функції потоку, і дизайн врахував це.

Але це також може бути досадно, якщо використовувати якусь допоміжну функцію, таку як функція ведення журналу. Наприклад, ви можете знайти вихід журналу раптово закритим, якщо трапляється викликати функцію реєстрації потоком, який також перерваний.

Прикро, що це настільки тонко, оскільки неврахування цього може призвести до помилок, які впливають на цілісність запису. [1] [2]


3

Я перевірив продуктивність FileInputStream vs. FileChannel для декодування файлів, кодованих base64. У своїх експериментах я перевіряв досить великий файл, і традиційний io був завжди трохи швидшим, ніж nio.

FileChannel, можливо, мав перевагу в попередніх версіях jvm через синхронізацію накладних витрат в декількох класах, пов'язаних з io, але сучасний jvm досить добре знімає непотрібні блоки.


2

Якщо ви не використовуєте функцію transferTo або функції, що не блокують, ви не помітите різниці між традиційним IO і NIO (2), оскільки традиційний IO відображається на NIO.

Але якщо ви можете використовувати такі функції NIO, як transferFrom / To або хочете використовувати буфери, то, звичайно, NIO - це шлях.


0

Мій досвід полягає в тому, що NIO набагато швидше з невеликими файлами. Але якщо мова йде про великі файли, FileInputStream / FileOutputStream відбувається набагато швидше.


4
Ви це переплутали? Мій власний досвід полягає в тому, що java.nioшвидше з більшими файлами, ніж java.io, не меншими.
Стю Томпсон

Ні, мій досвід - навпаки. java.nioшвидко, доки файл достатньо малий, щоб його відобразити в пам'яті. Якщо він стає більшим (200 Мб і більше), java.ioце швидше.
tangens

Ого. Повна протилежність мені. Зауважте, що вам не обов’язково потрібно картографувати файл, щоб прочитати його - можна прочитати з FileChannel.read(). Існує не один єдиний підхід до читання файлів за допомогою java.nio.
Стю Томпсон

2
@tangens Ви це перевірили?
sinedsem
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.