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


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

7 ответов:

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

размер буфера. алгоритм у вас в основном есть

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

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

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

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

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

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

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

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

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

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

мои тесты надежны и надежны, потому что они имели место на производственной системе, мясистой системе, системе под нагрузкой, собранной в журналах. не мой ноутбук 7200 RPM 2.5" SATA drive, пока я напряженно наблюдал, как 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 быстро работает только с небольшими файлами и становится очень медленным на больших файлах. НИО databuffer, с флип всегда превосходит стандартный ввод / вывод.

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

    1. NIO (transferFrom) ~2300ms
    2. NIO (direct databuffer 5000b flip) ~3500ms
    3. стандартного ввода / вывода (буфер 5000б) ~6000ms
  • копирование 100x20mb

    1. NIO (прямой databuffer 5000b флип) ~4000ms
    2. NIO (transferFrom) ~5000ms
    3. стандартный IO (буфер 5000b) ~6500ms
  • копирование 1x1000mb

    1. NIO (direct databuffer 5000b flip) ~4500s
    2. стандартный IO (буфер 5000b) ~7000ms
    3. NIO (transferFrom) ~8000ms

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

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

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

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

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

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

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

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

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

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

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