Файловый канал Java NIO в сравнении с производительностью / полезностью FileOutputStream

StackOverflow https://stackoverflow.com/questions/1605332

Вопрос

Я пытаюсь выяснить, есть ли какая-либо разница в производительности (или преимуществах) при использовании 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");
    }
}
Это было полезно?

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Другие советы

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

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:

  

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

Основываясь на моих тестах (Win7 64bit, 6GB RAM, Java6), NIO transferFrom работает быстро только с небольшими файлами и становится очень медленным для файлов большего размера.NIO databuffer flip всегда превосходит стандартный ввод-вывод.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top