Java NIO FileChannel против производительности / полезности FileOutputstream

150

Я пытаюсь выяснить, есть ли разница в производительности (или преимуществах), когда мы используем 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");
    }
}
  • 4
    transferTo / transferFrom будет более обычным для копирования файлов. Какая бы техника не делала ваш жесткий диск быстрее или медленнее, хотя я думаю, что это может быть проблемой, если он читает маленькие куски за раз и заставляет голову тратить слишком много времени на поиск.
  • 1
    (Вы не упоминаете, какую ОС вы используете, или какой поставщик и версию JRE.)
Показать ещё 1 комментарий
Теги:
optimization
file
nio

7 ответов

179
Лучший ответ

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

Размер буфера. Алгоритм, который вы в основном используете,

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

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

Не выполняйте тесты Java, которые читают и записывают на один и тот же диск.

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

Не используйте буфер, если вам не нужно.

Зачем копировать в память, если ваша цель - другой диск или сетевой адаптер? При больших файлах задержка не является тривиальной.

Как и другие, используйте FileChannel.transferTo() или FileChannel.transferFrom(). Ключевым преимуществом здесь является то, что JVM использует доступ к ОС для DMA (Прямой доступ к памяти), если присутствует. (Это зависит от реализации, но современные версии Sun и IBM для процессоров общего назначения хороши.) Что происходит, так это то, что данные поступают прямо на/из диска, на шину, а затем в пункт назначения... минуя любую схему через RAM или CPU.

Веб-приложение, в котором я провел дни и ночи, очень тяжело. Я сделал микро-тесты и реальные тесты. И результаты в моем блоге, посмотрите:

Использовать производственные данные и среды

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

Мои тесты являются прочными и надежными, потому что они происходили на производственной системе, в мясной системе, системе под нагрузкой, собранной в журналах. Не мой ноутбук 7200 об/мин 2,5-дюймовый SATA-накопитель, в то время как я наблюдал, как JVM работает на моем жестком диске.

Чем вы работаете? Это важно.

  • 0
    @Stu Томпсон - Спасибо за ваш пост. Я наткнулся на ваш ответ, так как я занимаюсь исследованиями по той же теме. Я пытаюсь понять улучшения ОС, которые nio предоставляет программистам Java. Пара из них - DMA и файлы с отображением в памяти. Вы встречали больше таких улучшений? PS - Ссылки на ваш блог не работают.
  • 0
    @AndyDufresne мой блог в данный момент закрыт, позже на этой неделе - в процессе его перемещения.
Показать ещё 3 комментария
33

Если вещь, которую вы хотите сравнить, - это производительность копирования файлов, то для теста канала вы должны сделать это:

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, 6GB RAM, Java6), NIO transferFrom быстро работает только с небольшими файлами и становится очень медленным в больших файлах. Флажок NIO databuffer всегда превосходит стандартный IO.

  • Копирование 1000x2MB

    • NIO (transferFrom) ~ 2300ms
    • NIO (прямой datababuffer 5000b flip) ~ 3500 мс
    • Стандартный IO (буфер 5000b) ~ 6000 мс
  • Копирование 100x20mb

    • NIO (прямой datababuffer 5000b flip) ~ 4000 мс
    • NIO (transferFrom) ~ 5000ms
    • Стандартный IO (буфер 5000b) ~ 6500 мс
  • Копирование 1x1000mb

    • NIO (прямой datababuffer 5000b flip) ~ 4500s
    • Стандартный IO (буфер 5000b) ~ 7000 мс
    • NIO (transferFrom) ~ 8000ms

Метод transferTo() работает с кусками файла; не был предназначен как способ копирования файлов высокого уровня: Как скопировать большой файл в Windows XP?

3

Я тестировал производительность FileInputStream и FileChannel для декодирования файлов с кодировкой base64. В моих экспериментах я тестировал довольно большой файл, а традиционный io всегда был быстрее, чем nio.

FileChannel, возможно, имел преимущество в предыдущих версиях jvm из-за накладных расходов на синхронизацию в нескольких связанных с ним классах, но современные jvm неплохо удаляют ненужные блокировки.

2

Отвечая на вопрос "полезность" вопроса:

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

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

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

Это, к сожалению, так тонко, потому что не учет этого может привести к ошибкам, которые влияют на целостность записи. [1] [2]

2

Если вы не используете функцию transferTo или неблокирующие функции, вы не заметите разницы между традиционными IO и NIO (2), поскольку традиционные IO-карты соответствуют NIO.

Но если вы можете использовать функции NIO, такие как transferFrom/To или хотите использовать Buffers, то, конечно, NIO - это путь.

-2

Мой опыт в том, что NIO намного быстрее с небольшими файлами. Но когда дело доходит до больших файлов, FileInputStream/FileOutputStream выполняется намного быстрее.

  • 4
    Вы перепутали? Мой собственный опыт java.nio что java.nio быстрее с большими файлами, чем java.io , а не меньше.
  • 0
    Нет, мой опыт наоборот. java.nio работает быстро, пока файл достаточно мал, чтобы отображаться в память. Если он становится больше (200 МБ и более), java.io быстрее.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню