Java NIO FileChannelとFileOutputstreamのパフォーマンス/有用性


169

ファイルシステムへのファイルの読み取りと書き込みにnioを使用する場合FileChannelと通常の場合を使用した場合のパフォーマンス(または利点)に違いがあるかどうかを把握しようとしFileInputStream/FileOuputStreamています。私は自分のマシンで両方とも同じレベルで実行することを観察しましたFileChannel。これら2つの方法を比較した詳細を教えてください。これが私が使用したコードです、私がテストしているファイルは周りにあり350MBます。ランダムアクセスやその他の高度な機能を検討していない場合、ファイルI / Oに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は、ファイルをコピーするためのより一般的な方法です。一度に小さなチャンクを読み取り、ヘッドがシークに膨大な時間を費やすようになると、問題が発生する可能性がありますが、ハードドライブを速くしたり遅くしたりするべきではない方法です。
トム・ホーティン-

1
(どのOSを使用しているか、どのJREベンダーとバージョンかについては言及していません。)
Tom Hawtin-

申し訳ありません。SunJDK6でFC10を使用しています。
ケシャフ

回答:


202

大きなファイルサイズでの私の経験は、それjava.nioよりも速いですjava.io断然速い。 > 250%の範囲のように。そうは言っても、私はあなたのマイクロベンチマークが苦しむかもしれない明らかなボトルネックを排除しています。調査する可能性のある領域:

バッファサイズ。 あなたが基本的に持っているアルゴリズムは

  • ディスクからバッファにコピー
  • バッファからディスクにコピーする

私自身の経験では、このバッファーサイズは 熟しチューニングのため。アプリケーションの一部は4KB、別の部分は256KBで解決しました。私はあなたのコードがそのような大きなバッファで苦しんでいると思います。1KB、2KB、4KB、8KB、16KB、32KB、64KBのバッファーでいくつかのベンチマークを実行して、それを証明します。

同じディスクに対して読み取りと書き込みを行うJavaベンチマークを実行しないでください。

その場合、ディスクではなくJavaのベンチマークを実際に行っていることになります。また、CPUがビジーでない場合は、おそらく他のボトルネックが発生していることをお勧めします。

必要がない場合は、バッファを使用しないでください。

ターゲットが別のディスクまたはNICである場合、なぜメモリにコピーするのですか?大きなファイルでは、発生するレイテンシは重要です。

他の人が言ったように、FileChannel.transferTo()またはを使用しますFileChannel.transferFrom()。ここでの主な利点は、存在する場合、JVMがDMAへのOSのアクセス(ダイレクトメモリアクセス)を使用することです。(これは実装に依存しますが、汎用CPUでの最新のSunおよびIBMバージョンは問題ありません。) データは、ディスクとバスの間を直接行き、次に宛先に行きます...回路をバイパスしてRAMまたはCPU。

私が昼夜を費やして作業したWebアプリは、IOが非常に重いです。マイクロベンチマークと実際のベンチマークも実施しました。そして結果は私のブログにアップされています、見てください:

本番データと環境を使用する

マイクロベンチマークはゆがみがちです。可能であれば、期待するハードウェアで、期待する負荷で、計画しているとおりにデータを収集するように努力してください。

私のベンチマークは、ログに収集された本番システム、負荷の高いシステム、負荷のかかったシステムで行われたため、しっかりしていて信頼できます。 JVMがハードディスクを動かすのを見ている間、ノートブックの7200 RPM 2.5インチSATAドライブではありませんでした。

何をしているの?それは重要です。


@Stu Thompson-投稿ありがとうございます。私は同じトピックについて研究しているので、あなたの答えに出会いました。nioがJavaプログラマーに公開しているOSの改善点を理解しようとしています。それらのいくつかは-DMAおよびメモリマップファイルです。そのような改善の多くに遭遇しましたか?PS-ブログのリンクが壊れています。
アンディDufresne 2013年

@AndyDufresne私のブログは現在ダウンしていますが、今週後半に-移動の過程でアップします。
Stu Thompson


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();

これは、1つのチャネルから別のチャネルへのバッファリングよりも遅くはなく、大幅に高速になる可能性があります。Javadocsによると:

多くのオペレーティングシステムは、実際にバイトをコピーせずに、バイトをファイルシステムキャッシュからターゲットチャネルに直接転送できます。


7

私のテスト(Win7 64ビット、6GB RAM、Java6)に基づいて、NIO transferFromは小さなファイルでのみ高速になり、大きなファイルでは非常に遅くなります。NIOデータバッファのフリップは常に標準IOよりも優れています。

  • 1000x2MBをコピーしています

    1. NIO(transferFrom)〜2300ms
    2. NIO(直接データバッファ5000bフリップ)〜3500ms
    3. 標準IO(バッファ5000b)〜6000ms
  • 100x20mbをコピーしています

    1. NIO(直接データバッファ5000bフリップ)〜4000ms
    2. NIO(transferFrom)〜5000ms
    3. 標準IO(バッファ5000b)〜6500ms
  • 1x1000mbのコピー

    1. NIO(直接データバッファ5000bフリップ)〜4500s
    2. 標準IO(バッファ5000b)〜7000ms
    3. NIO(transferFrom)〜8000ms

transferTo()メソッドは、ファイルのチャンクに対して機能します。高レベルのファイルコピー方法として意図されていなかった: Windows XPで大きなファイルをコピーする方法?


6

質問の「有用性」の部分に答える:

FileChannelover を使用することのやや微妙な問題の1つは、割り込み状態にあるスレッドからFileOutputStreamブロック操作(read()またはwrite())を実行すると、チャネルがで突然閉じることです。java.nio.channels.ClosedByInterruptException

さて、これが何FileChannelに使用されていてもスレッドのメイン関数の一部であり、デザインがこれを考慮に入れていれば、これは良いことかもしれません。

ただし、ロギング機能などの補助機能で使用する場合も厄介です。たとえば、ロギング関数が中断されたスレッドによって偶然に呼び出された場合、ロギング出力が突然閉じられたことがわかります。

これを考慮に入れないと、書き込みの整合性に影響を与えるバグが発生する可能性があるため、これは非常に微妙です。[1] [2]


3

Base64でエンコードされたファイルのデコードについて、FileInputStreamとFileChannelのパフォーマンスをテストしました。私の経験では、かなり大きなファイルをテストしましたが、従来のioは常にnioよりも少し高速でした。

以前のバージョンのjvmでは、いくつかのio関連クラスの同期オーバーヘッドのためにFileChannelが有利だったかもしれませんが、最近のjvmは不要なロックを削除するのに非常に適しています。


2

transferTo機能または非ブロッキング機能を使用していない場合、従来のIOはNIOにマッピングされるため、従来のIOとNIO(2)の違いはわかりません。

しかし、transferFrom / ToなどのNIO機能を使用できる場合、またはバッファーを使用したい場合は、もちろんNIOが適しています。


0

私の経験では、NIOは小さいファイルを使用した方がはるかに高速です。ただし、大きなファイルの場合、FileInputStream / FileOutputStreamの方がはるかに高速です。


4
混同しましたか?私自身の経験では、ファイルが大きいほどjava.nio速く、小さくはありません。java.io
Stu Thompson、

いいえ、私の経験は逆です。java.nioファイルがメモリにマップされるのに十分小さい限り、高速です。大きくなった場合(200 MB以上)java.ioは高速です。
タンジェンス2009年

ワオ。私の反対です。ファイルを読み取るために必ずしもマップする必要はないことに注意してください-から読み取ることができますFileChannel.read()。を使用してファイルを読み取る方法は1つだけではありませんjava.nio
Stu Thompson、

2
@tangensはこれを確認しましたか?
sinedsem
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.