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будет более обычным для копирования файлов. Какой бы метод не делал ваш жесткий диск быстрее или медленнее, хотя я думаю, что это может быть проблемой, если он читает маленькие куски за раз и заставляет голову тратить слишком много времени на поиск.
Том Хотин - tackline

1
(Вы не упоминаете, какую ОС вы используете, или какой поставщик и версию JRE.)
Том Хотин - tackline

К сожалению, я использую FC10 с Sun JDK6.
Кешав

Ответы:


202

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

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

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

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

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

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

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

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

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

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

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

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

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

На чем ты бежишь? Это имеет значение.


@Stu Томпсон - Спасибо за ваш пост. Я наткнулся на ваш ответ, так как я занимаюсь исследованиями по той же теме. Я пытаюсь понять улучшения ОС, которые 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

Основываясь на моих тестах (64-битная Win7, 6 ГБ ОЗУ, Java6), NIO TransferFrom работает быстро только с небольшими файлами и очень медленно работает с большими файлами. Отворот буфера данных NIO всегда превосходит стандартный ввод-вывод.

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

    1. NIO (передача от) ~ 2300мс
    2. NIO (прямой откат данных 5000b) ~ 3500 мс
    3. Стандартный ввод-вывод (буфер 5000b) ~ 6000 мс
  • Копирование 100x20mb

    1. NIO (прямой откат данных 5000b) ~ 4000 мс
    2. НИО (передача от) ~ 5000мс
    3. Стандартный ввод-вывод (буфер 5000b) ~ 6500 мс
  • Копирование 1x1000mb

    1. NIO (прямой откат данных 5000b) ~ 4500 с
    2. Стандартный ввод-вывод (буфер 5000b) ~ 7000 мс
    3. НИО (передача от) ~ 8000мс

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


6

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

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

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

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

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


3

Я проверил производительность FileInputStream и 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это быстрее.
тангенс

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

2
@tangens ты это проверял?
Синедсем
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.