Java NIO FileChannelとFileOutputstreamのパフォーマンス/有用性
-
05-07-2019 - |
質問
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");
}
}
解決
大きなファイルサイズでの私の経験では、 java.nio
は java.io
よりも高速です。 非常に高速。> 250%の範囲と同様。そうは言っても、明らかなボトルネックを排除しているので、マイクロベンチマークに悩まされる可能性があります。調査する可能性のある分野:
バッファサイズ。基本的に持っているアルゴリズムは
- ディスクからバッファにコピー
- バッファからディスクへのコピー
私自身の経験では、このバッファサイズはチューニングのために 熟成 しています。私は、アプリケーションのある部分に4KB、別の部分に256KBを決定しました。あなたのコードはこのような大きなバッファに苦しんでいると思います。 1KB、2KB、4KB、8KB、16KB、32KB、および64KBのバッファーを使用してベンチマークを実行し、自分自身で証明します。
同じディスクを読み書きするJavaベンチマークを実行しないでください。
もしそうなら、あなたは本当にJavaではなくディスクのベンチマークを行っています。また、CPUがビジーでない場合は、おそらく他のボトルネックが発生していることをお勧めします。
必要がない場合はバッファを使用しないでください。
ターゲットが別のディスクまたはNICである場合、なぜメモリにコピーしますか?ファイルが大きい場合、発生する遅延は重要です。
他の人が言ったように、 FileChannel.transferTo()
または FileChannel.transferFrom()
を使用します。ここでの主な利点は、JVMがOSのDMAへのアクセス(ダイレクトメモリアクセス)を使用することです。プレゼント。 (これは実装に依存しますが、汎用CPUの最新のSunおよびIBMバージョンは問題ありません。)データはディスク、バス、そして宛先に直接送られます。 ... RAMまたはCPUを介して回路をバイパスします。
私が昼夜を費やして作業したWebアプリは、IOが非常に重いです。マイクロベンチマークと実際のベンチマークも行っています。結果は私のブログに掲載されています。ご覧ください:
本番データと環境を使用
マイクロベンチマークは歪みやすい傾向があります。可能であれば、期待するハードウェア上で、期待する負荷で、予定通りのデータを収集するよう努力してください。
私のベンチマークは、実稼働システム、強力なシステム、負荷がかかっているシステムでログに収集されたため、堅実で信頼性があります。ノートブックの7200 RPM 2.5" ではない JVMがハードディスクを動作させている間、激しく見ている間にSATAドライブ。
何を実行していますか?重要です。
他のヒント
比較したいことがファイルコピーのパフォーマンスである場合、チャネルテストでは代わりにこれを行う必要があります。
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 64ビット、6GB RAM、Java6)に基づくと、NIO transferFromは小さなファイルでのみ高速で、大きなファイルでは非常に遅くなります。 NIOデータバッファフリップは常に標準IOよりも優れています。
-
1000x2MBのコピー
- NIO(transferFrom)〜2300ms
- NIO(ダイレクトデータババッファー5000bフリップ)〜3500ms
- 標準IO(バッファ5000b)〜6000ms
-
100x20mbのコピー
- NIO(直接データババッファー5000bフリップ)〜4000ms
- NIO(transferFrom)〜5000ms
- 標準IO(バッファ5000b)〜6500ms
-
1x1000mbのコピー
- NIO(ダイレクトデータババッファー5000bフリップ)〜4500s
- 標準IO(バッファ5000b)〜7000ms
- NIO(transferFrom)〜8000ms
transferTo()メソッドは、ファイルのチャンクに対して機能します。高レベルのファイルコピー方法として意図されていませんでした: Windows XPで大きなファイルをコピーする方法は?
「有用性」への回答質問の一部:
FileOutputStream
ではなく FileChannel
を使用することの微妙な落とし穴の1つは、そのブロッキング操作(たとえば、 read()
または ) 「noreferrer」>割り込み状態により、
java.nio.channels.ClosedByInterruptException
。
今、 FileChannel
が使用されたものがスレッドのメイン関数の一部である場合、これは良いことであり、設計はこれを考慮しました。
しかし、ロギング機能などの補助機能によって使用されると厄介な場合もあります。たとえば、ロギング機能が中断されたスレッドによって呼び出された場合、ロギング出力が突然閉じられていることがわかります。
これを考慮すると、書き込みの整合性に影響するバグが発生する可能性があるため、これは非常に微妙です。 [1] [2]
base64でエンコードされたファイルのデコードについて、FileInputStreamとFileChannelのパフォーマンスをテストしました。私の経験では、かなり大きなファイルをテストし、従来のioは常にnioよりも少し高速でした。
FileChannelは、以前のバージョンのjvmでいくつかのio関連クラスの同期化オーバーヘッドのために有利だったかもしれませんが、最新のjvmは不要なロックを削除するのに非常に優れています。
transferTo機能または非ブロッキング機能を使用していない場合、従来のIOはNIOにマッピングされるため、従来のIOとNIO(2)の違いに気付かないでしょう。
ただし、transferFrom / ToなどのNIO機能を使用できる場合、またはBuffersを使用する場合は、もちろんNIOが最適です。
私の経験では、NIOは小さなファイルではるかに高速です。ただし、大きなファイルの場合、FileInputStream / FileOutputStreamははるかに高速です。