グーグルしながら、私はそれを使用して java.io.File#length()
が遅くなることがあります。
FileChannel
持っているsize()
だけでなく、利用可能である方法を。
Javaでファイルサイズを取得する効率的な方法はありますか?
グーグルしながら、私はそれを使用して 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;
}
}
stream.available()
ファイルの長さは返しません。他のストリームをブロックせずに読み取りに使用できるバイト数を返します。ファイルの長さと同じバイト数である必要はありません。ストリームから実際の長さを取得するには、実際にそれを読み取る必要があります(その間、読み取りバイトをカウントします)。
GHadによって与えられたベンチマークは、長さを取得する以外にも、他の多くの要素(反射、インスタンス化など)を測定します。これらのものを取り除こうとすると、1回の呼び出しで次の時間がマイクロ秒で取得されます。
ファイル合計___ 19.0、反復ごとに___ 19.0 raf sum ___ 16.0、反復ごとに___ 16.0 チャネルsum__273.0、Iteration__273.0ごと
100回の実行と10000回の反復の場合、次のようになります。
Iteration__1.7676290000000001あたりのファイルsum__1767629.0 raf sum ___ 881284.0、Iteration__0.8812840000000001ごと チャネルsum ___ 414286.0、Iteration__0.414286ごと
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
これらのクラスはファイルを読み取るためにストリームを開くため、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";
}
}
}
私はこれと同じ問題に遭遇しました。ファイルサイズとネットワーク共有上の90,000ファイルの変更日を取得する必要がありました。Javaを使用し、可能な限り最小限に抑えるには、非常に長い時間がかかります。(ファイルからURLとオブジェクトのパスも取得する必要がありました。そのため多少異なりますが、1時間以上です。)次に、ネイティブのWin32実行可能ファイルを使用して同じタスクを実行し、ファイルをダンプしました。パス、変更、サイズをコンソールに設定し、Javaから実行します。スピードはすごかった。ネイティブプロセス、およびデータを読み取るための私の文字列処理では、1秒あたり1000を超えるアイテムを処理できます。
したがって、人々は上記のコメントをランクダウンしましたが、これは有効な解決策であり、私の問題を解決しました。私の場合、必要なサイズのフォルダーを事前に知っていて、それをコマンドラインでwin32アプリに渡すことができました。ディレクトリの処理に数時間から数分かかりました。
この問題は、Windows固有の問題のようにも思われました。OS Xには同じ問題がなく、OSができるのと同じ速さでネットワークファイル情報にアクセスできました。
WindowsでのJavaファイルの処理はひどいものです。ただし、ファイルのローカルディスクアクセスは問題ありません。ひどいパフォーマンスを引き起こしたのはネットワーク共有だけでした。Windowsはネットワーク共有に関する情報を取得し、1分もかからずに合計サイズを計算できます。
-ベン
ディレクトリ内の複数のファイルのファイルサイズが必要な場合は、を使用しますFiles.walkFileTree
。サイズはBasicFileAttributes
お手元に届いたものから入手できます。
これは.length()
、の結果を呼び出すFile.listFiles()
か、の結果を使用するよりもはるかに高速Files.size()
ですFiles.newDirectoryStream()
。私のテストケースでは、約100倍高速でした。
Files.walkFileTree
参考までに、Android 26 以降で利用できます。
実際、「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 1が単独で実行される場合:
CHANNEL sum: 59691, per Iteration: 238.764
LENGTHが単独で実行される場合:
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();
}