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

Я пытаюсь выяснить, есть ли разница в производительности (или преимуществах), когда мы используем nio FileChannel против нормального FileInputStream/FileOuputStream для чтения и записи файлов в файловую систему. Я заметил, что на моей машине оба выполняют на одном уровне, также много раз FileChannel путь медленнее. Могу ли я узнать больше деталей, сравнивая эти два метода. Вот код, который я использовал, файл, который я тестирую, находится вокруг 350MB. Это хороший вариант использовать классы на основе NIO для ввода-вывода файлов, если я не глядя на random access или другие такие расширенные функции?

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 ответов


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

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

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

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

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

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

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

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

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

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

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

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

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

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

    1. NIO (transferFrom) ~2300ms
    2. NIO (прямой datababuffer 5000b флип) ~3500ms
    3. стандартный IO (буфер 5000b) ~6000ms
  • копирование 100x20mb

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

    1. NIO (прямой datababuffer 5000b флип) ~4500s
    2. стандартный IO (буфер 5000b) ~7000ms
    3. NIO (transferFrom) ~8000ms

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


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

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


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

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

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

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

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


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

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


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