グーグル検索中に、 Java.io.File#length()
を使用すると速度が遅くなることがあります。 FileChannel
には size()
メソッドもあります。
Javaでファイルサイズを取得する効率的な方法はありますか?
さて、私は以下のコードでそれを測定しようとしました:
実行数= 1および反復数= 1の場合、URLメソッドが最も速く、その後にチャネルが続きます。私はこれを約10回新鮮に一時停止して実行します。したがって、一度だけアクセスする場合、URLを使用することは、私が考えることができる最速の方法です。
LENGTH sum: 10626, per Iteration: 10626.0
CHANNEL sum: 5535, per Iteration: 5535.0
URL sum: 660, per Iteration: 660.0
実行数= 5および反復数= 50の場合、絵は異なります。
LENGTH sum: 39496, per Iteration: 157.984
CHANNEL sum: 74261, per Iteration: 297.044
URL sum: 95534, per Iteration: 382.136
ファイルはファイルシステムへの呼び出しをキャッシュする必要がありますが、チャネルとURLにはオーバーヘッドがあります。
コード:
import Java.io.*;
import Java.net.*;
import Java.util.*;
public enum FileSizeBench {
LENGTH {
@Override
public long getResult() throws Exception {
File me = new File(FileSizeBench.class.getResource(
"FileSizeBench.class").getFile());
return me.length();
}
},
CHANNEL {
@Override
public long getResult() throws Exception {
FileInputStream fis = null;
try {
File me = new File(FileSizeBench.class.getResource(
"FileSizeBench.class").getFile());
fis = new FileInputStream(me);
return fis.getChannel().size();
} finally {
fis.close();
}
}
},
URL {
@Override
public long getResult() throws Exception {
InputStream stream = null;
try {
URL url = FileSizeBench.class
.getResource("FileSizeBench.class");
stream = url.openStream();
return stream.available();
} finally {
stream.close();
}
}
};
public abstract long getResult() throws Exception;
public static void main(String[] args) throws Exception {
int runs = 5;
int iterations = 50;
EnumMap<FileSizeBench, Long> durations = new EnumMap<FileSizeBench, Long>(FileSizeBench.class);
for (int i = 0; i < runs; i++) {
for (FileSizeBench test : values()) {
if (!durations.containsKey(test)) {
durations.put(test, 0l);
}
long duration = testNow(test, iterations);
durations.put(test, durations.get(test) + duration);
// System.out.println(test + " took: " + duration + ", per iteration: " + ((double)duration / (double)iterations));
}
}
for (Map.Entry<FileSizeBench, Long> entry : durations.entrySet()) {
System.out.println();
System.out.println(entry.getKey() + " sum: " + entry.getValue() + ", per Iteration: " + ((double)entry.getValue() / (double)(runs * iterations)));
}
}
private static long testNow(FileSizeBench test, int iterations)
throws Exception {
long result = -1;
long before = System.nanoTime();
for (int i = 0; i < iterations; i++) {
if (result == -1) {
result = test.getResult();
//System.out.println(result);
} else if ((result = test.getResult()) != result) {
throw new Exception("variance detected!");
}
}
return (System.nanoTime() - before) / 1000;
}
}
GHadが提供するベンチマークは、長さを取得する以外に、他の多くのもの(リフレクション、オブジェクトのインスタンス化など)を測定します。これらを削除しようとすると、1回の呼び出しで次の時間がマイクロ秒単位で取得されます。
ファイルsum ___ 19.0、Iteration ___ 19.0 あたりraf sum ___ 16.0、Iteration ___ 16.0 channel sum__273.0、Iteration__273.0 ごと
100回の実行と10000回の繰り返しで、
[。
次の変更されたコードを実行して、引数として100MBファイルの名前を指定しました。
import Java.io.*;
import Java.nio.channels.*;
import Java.net.*;
import Java.util.*;
public class FileSizeBench {
private static File file;
private static FileChannel channel;
private static RandomAccessFile raf;
public static void main(String[] args) throws Exception {
int runs = 1;
int iterations = 1;
file = new File(args[0]);
channel = new FileInputStream(args[0]).getChannel();
raf = new RandomAccessFile(args[0], "r");
HashMap<String, Double> times = new HashMap<String, Double>();
times.put("file", 0.0);
times.put("channel", 0.0);
times.put("raf", 0.0);
long start;
for (int i = 0; i < runs; ++i) {
long l = file.length();
start = System.nanoTime();
for (int j = 0; j < iterations; ++j)
if (l != file.length()) throw new Exception();
times.put("file", times.get("file") + System.nanoTime() - start);
start = System.nanoTime();
for (int j = 0; j < iterations; ++j)
if (l != channel.size()) throw new Exception();
times.put("channel", times.get("channel") + System.nanoTime() - start);
start = System.nanoTime();
for (int j = 0; j < iterations; ++j)
if (l != raf.length()) throw new Exception();
times.put("raf", times.get("raf") + System.nanoTime() - start);
}
for (Map.Entry<String, Double> entry : times.entrySet()) {
System.out.println(
entry.getKey() + " sum: " + 1e-3 * entry.getValue() +
", per Iteration: " + (1e-3 * entry.getValue() / runs / iterations));
}
}
}
この投稿のすべてのテストケースは、テストされた各メソッドで同じファイルにアクセスするため、欠陥があります。そのため、テスト2と3の恩恵を受けるディスクキャッシュが開始されます。私のポイントを証明するために、GHADが提供するテストケースを取り上げ、列挙の順序を変更しました。結果は以下のとおりです。
結果を見ると、File.length()が本当に勝者だと思います。
テストの順序は出力の順序です。私のマシンでの実行時間は実行ごとに異なりますが、File.Length()が最初ではなく、最初のディスクアクセスが発生した場合に勝ちます。
---
LENGTH sum: 1163351, per Iteration: 4653.404
CHANNEL sum: 1094598, per Iteration: 4378.392
URL sum: 739691, per Iteration: 2958.764
---
CHANNEL sum: 845804, per Iteration: 3383.216
URL sum: 531334, per Iteration: 2125.336
LENGTH sum: 318413, per Iteration: 1273.652
---
URL sum: 137368, per Iteration: 549.472
LENGTH sum: 18677, per Iteration: 74.708
CHANNEL sum: 142125, per Iteration: 568.5
リソースの代わりに絶対パスでアクセスされるファイルを使用するようにコードを変更すると、異なる結果が得られます(1回の実行、1回の反復、100,000バイトのファイルに対して、10バイトのファイルの時間は100,000バイトと同じです) )
長さの合計:33、反復あたり:33.0
チャンネル合計:3626、反復あたり:3626.0
URL合計:294、反復あたり:294.0
私はこの同じ問題に出くわしました。ネットワーク共有上の90,000ファイルのファイルサイズと変更日を取得する必要がありました。 Javaを使用し、可能な限り最小限に抑えるには、非常に長い時間がかかります。 (ファイルからURLとオブジェクトのパスも取得する必要がありました。そのため、多少異なりますが、1時間以上かかりました。)その後、ネイティブWin32実行可能ファイルを使用し、同じタスクを実行して、ファイルをダンプしました。コンソールへのパス、変更、サイズ、およびJavaからの実行。スピードはすごかった。ネイティブプロセス、およびデータを読み取るための文字列処理は、1秒間に1000を超えるアイテムを処理できます。
したがって、人々は上記のコメントを下にランク付けしましたが、これは有効なソリューションであり、私の問題を解決しました。私の場合、事前に必要なサイズのフォルダーがわかっていたので、コマンドラインでwin32アプリに渡すことができました。ディレクトリを数時間から数分に処理しました。
この問題は、Windows固有の問題でもあるようです。 OS Xには同じ問題はなく、OSができる限り速くネットワークファイル情報にアクセスできました。
WindowsでのJavaファイルの処理はひどいです。ただし、ファイルのローカルディスクアクセスは問題ありません。ひどいパフォーマンスを引き起こしたのは、ネットワーク共有だけでした。 Windowsは、ネットワーク共有に関する情報を取得し、1分以内に合計サイズを計算することもできます。
-ベン
Rgrigのベンチマークに応じて、FileChannelおよびRandomAccessFileインスタンスのオープン/クローズにかかる時間も考慮する必要があります。これらのクラスはファイルを読み取るためにストリームを開くためです。
ベンチマークを変更した後、85MBファイルで1回繰り返してこれらの結果を取得しました。
file totalTime: 48000 (48 us)
raf totalTime: 261000 (261 us)
channel totalTime: 7020000 (7 ms)
同じファイルで10000回反復する場合:
file totalTime: 80074000 (80 ms)
raf totalTime: 295417000 (295 ms)
channel totalTime: 368239000 (368 ms)
必要なのがファイルサイズだけである場合、file.length()がそれを行う最も速い方法です。読み取り/書き込みなどの他の目的にファイルを使用する場合は、RAFの方が適しているようです。ファイル接続を閉じることを忘れないでください:-)
import Java.io.File;
import Java.io.FileInputStream;
import Java.io.RandomAccessFile;
import Java.nio.channels.FileChannel;
import Java.util.HashMap;
import Java.util.Map;
public class FileSizeBench
{
public static void main(String[] args) throws Exception
{
int iterations = 1;
String fileEntry = args[0];
Map<String, Long> times = new HashMap<String, Long>();
times.put("file", 0L);
times.put("channel", 0L);
times.put("raf", 0L);
long fileSize;
long start;
long end;
File f1;
FileChannel channel;
RandomAccessFile raf;
for (int i = 0; i < iterations; i++)
{
// file.length()
start = System.nanoTime();
f1 = new File(fileEntry);
fileSize = f1.length();
end = System.nanoTime();
times.put("file", times.get("file") + end - start);
// channel.size()
start = System.nanoTime();
channel = new FileInputStream(fileEntry).getChannel();
fileSize = channel.size();
channel.close();
end = System.nanoTime();
times.put("channel", times.get("channel") + end - start);
// raf.length()
start = System.nanoTime();
raf = new RandomAccessFile(fileEntry, "r");
fileSize = raf.length();
raf.close();
end = System.nanoTime();
times.put("raf", times.get("raf") + end - start);
}
for (Map.Entry<String, Long> entry : times.entrySet()) {
System.out.println(entry.getKey() + " totalTime: " + entry.getValue() + " (" + getTime(entry.getValue()) + ")");
}
}
public static String getTime(Long timeTaken)
{
if (timeTaken < 1000) {
return timeTaken + " ns";
} else if (timeTaken < (1000*1000)) {
return timeTaken/1000 + " us";
} else {
return timeTaken/(1000*1000) + " ms";
}
}
}
ディレクトリ内の複数のファイルのファイルサイズが必要な場合は、 Files.walkFileTree
を使用します。受け取るBasicFileAttributes
からサイズを取得できます。
これは、.length()
の結果でFile.listFiles()
を呼び出すか、Files.size()
の結果でFiles.newDirectoryStream()
を使用するよりもはるかに高速です。私のテストケースでは、約100倍高速でした。
実際、「ls」の方が速いと思います。ファイル情報の取得に関するJavaには、間違いなくいくつかの問題があります。残念ながら、Windows用の再帰的なlsの同等の安全な方法はありません。 (cmd.exeのDIR/Sは混乱し、無限ループでエラーを生成する可能性があります)
XPでLAN上のサーバーにアクセスすると、Windowsでフォルダー内のファイル数(33,000)と合計サイズを取得するのに5秒かかります。
Javaでこれを再帰的に繰り返すと、5分以上かかります。 file.length()、file.lastModified()、およびfile.toURI()を実行するのにかかる時間の測定を開始しましたが、私が見つけたのは、時間の99%がこれらの3つの呼び出しにかかっていることです。実際に行う必要がある3つの呼び出し...
1000ファイルの違いは、ローカルで15ミリ秒、サーバーで1800ミリ秒です。 Javaでのサーバーパススキャンはとんでもなく遅いです。ネイティブOSが同じフォルダを高速にスキャンできる場合、なぜJavaはできないのですか?
より完全なテストとして、XPでWineMergeを使用して、サーバー上のファイルとローカルのファイルの変更日、サイズを比較しました。これは、各フォルダ内の33,000個のファイルのディレクトリツリー全体で繰り返されていました。合計時間、7秒。 Java:5分以上。
したがって、OPからの元のステートメントと質問は真実であり、有効です。ローカルファイルシステムを扱う場合は、目立ちません。 33,000のアイテムを含むフォルダーのローカル比較を行うには、WinMergeでは3秒かかり、Javaでは32秒かかります。繰り返しますが、Javaはネイティブに対して、これらの初歩的なテストでは10倍遅くなります。
Java 1.6.0_22(最新)、ギガビットLAN、およびネットワーク接続、pingは1ミリ秒未満(両方とも同じスイッチ内)
Javaは遅いです。
GHadのベンチマークから、人々が言及したいくつかの問題があります:
1>前述のBalusCのように:この場合、stream.available()がフローされます。
Available()は、この入力ストリームのメソッドの次の呼び出しによってブロックせずに、この入力ストリームから読み取ることができる(またはスキップできる)バイト数の推定を返すためです。
したがって、最初にこのアプローチでURLを削除します。
2> StuartHが述べたように-テストの実行順序もキャッシュの違いを生むので、テストを個別に実行してそれを取り除きます。
テストを開始します。
CHANNEL oneが単独で実行される場合:
CHANNEL sum: 59691, per Iteration: 238.764
LENGTH oneが単独で実行される場合:
LENGTH sum: 48268, per Iteration: 193.072
LENGTHが勝者のようです。
@Override
public long getResult() throws Exception {
File me = new File(FileSizeBench.class.getResource(
"FileSizeBench.class").getFile());
return me.length();
}