web-dev-qa-db-ja.com

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

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");
    }
}
164
Keshav

大きなファイルサイズでの私の経験では、Java.nioJava.ioよりも高速です。 非常に高速です。250%を超える範囲と同様です。そうは言っても、明らかなボトルネックを排除しているので、マイクロベンチマークに悩まされる可能性があります。調査する可能性のある分野:

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

  • ディスクからバッファにコピー
  • バッファからディスクへのコピー

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

同じディスクを読み書きするJavaベンチマークを実行しないでください。

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

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

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

他の人が言ったように、FileChannel.transferTo()またはFileChannel.transferFrom()を使用します。ここでの主な利点は、JVMがDMA( Direct Memory Access )へのOSのアクセスを使用することです(存在する場合)。 (これは実装に依存しますが、汎用CPU上の最新のSunおよびIBMバージョンは問題ありません。)何が起こるかは、ディスク、バス、そして、宛先に... RAMまたはCPUを介して回路をバイパスします。

私が昼夜を問わず作業に費やしたWebアプリは非常にIO重いです。マイクロベンチマークと実際のベンチマークも行っています。そして、結果は私のブログに掲載されています。

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

マイクロベンチマークには歪みが生じやすい。可能であれば、期待するハードウェア上で、期待する負荷で、予定通りのデータを収集する努力をしてください。

私のベンチマークは、ログに収集された実稼働システム、強力なシステム、負荷のかかったシステムで行われたため、堅実で信頼性があります。 Not私のノートブックの7200 RPM 2.5インチSATAドライブは、JVMがハードディスクを動作するのを激しく見つめていました。

何を実行していますか?重要です。

194
Stu Thompson

比較したいことがファイルコピーのパフォーマンスである場合、チャネルテストの代わりにこれを行う必要があります。

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によると:

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

38
uckelman

私のテスト(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フリップ)〜4500秒
    2. 標準IO(バッファ5000b)〜7000ms
    3. NIO(transferFrom)〜8000ms

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

7
noxo

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

FileChannelを介してFileOutputStreamを使用することの微妙な落とし穴の1つは、 割り込み状態 にあるスレッドからブロッキング操作(たとえばread()またはwrite())を実行すると、チャネルが Java.nio.channels.ClosedByInterruptException で突然閉じます。

これで、FileChannelが使用されたものがスレッドのメイン関数の一部である場合、これは良いことであり、設計ではこれを考慮しました。

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

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

5
antak

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

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

3
Jörn Horstmann

TransferTo機能または非ブロッキング機能を使用していない場合、従来のIOとNIO(2)の違いに気付くことはありません。これは、従来のIOがNIOにマッピングされるためです。

ただし、transferFrom/ToなどのNIO機能を使用できる場合、またはバッファーを使用する場合は、当然のことながらNIOが最適です。

2
eckes

私の経験では、NIOは小さなファイルではるかに高速です。しかし、大きなファイルの場合、FileInputStream/FileOutputStreamははるかに高速です。

0
tangens