特定のディレクトリのすべてのサブディレクトリを一覧表示する非常に単純なプログラムがあるとします。簡単に聞こえますか? Javaのすべてのサブディレクトリを一覧表示する唯一の方法は、 FilenameFilter を File.list() と組み合わせて使用することです。
これは些細なケースでも機能しますが、フォルダーに150,000ファイルと2つのサブフォルダーがある場合、すべてのファイルを繰り返し処理してfile.isDirectory()をテストするのを45秒間待つのはばかげています。サブディレクトリを一覧表示するより良い方法はありますか?
PS。申し訳ありませんが、同じディレクトリにファイルが多すぎるという講義を保存してください。私たちのライブ環境には、要件の一部としてこれがあります。
すでに述べたように、これは基本的にハードウェアの問題です。ディスクアクセスは常に低速であり、ほとんどのファイルシステムは、実際にはそれほど多くのファイルを含むディレクトリを処理するようには設計されていません。
何らかの理由ですべてのファイルを同じディレクトリに保存する必要がある場合は、独自のキャッシュを維持する必要があると思います。これは、sqlite、HeidiSQL、HSQLなどのローカルデータベースを使用して実行できます。極端なパフォーマンスが必要な場合は、Java TreeSetを使用して、メモリにキャッシュします。これは、少なくとも、ディレクトリを読み取る頻度が少なくて済み、次の場所で実行できる可能性があることを意味します。システムのネイティブファイル更新通知API(Linuxではinotify)を使用してディレクトリへの変更をサブスクライブすることにより、リストをさらに更新する必要性を減らすことができます。
これはあなたには不可能のようですが、私はかつてファイルをサブディレクトリに「ハッシュ」することで同様の問題を解決しました。私の場合、課題は数値IDを持つ数百万の画像を保存することでした。次のようにディレクトリ構造を構築しました。
images/[id - (id % 1000000)]/[id - (id % 1000)]/[id].jpg
これは私たちにとってうまく機能しており、私がお勧めするソリューションです。ファイル名の最初の2文字を取得し、次に次の2文字を取得するだけで、英数字のファイル名と同様のことができます。私もこれを一度やったことがあります、そしてそれは同様に仕事をしました。
可能なサブディレクトリ名の有限リストを知っていますか?その場合は、考えられるすべての名前をループして、ディレクトリの存在を確認してください。
そうしないと、ほとんどの基盤となるOSでディレクトリ名のみを取得できません(たとえば、Unixでは、ディレクトリリストは単に「ディレクトリ」ファイルの内容を読み取るだけなので、すべてのファイルをリストせずに「ディレクトリだけ」をすばやく見つける方法はありません)。
ただし、Java7のNIO.2( http://Java.Sun.com/developer/technicalArticles/javase/nio/# を参照)では、ストリーミングディレクトリリストを作成する方法があります。メモリ/ネットワークを乱雑にするファイル要素の完全な配列を取得しないでください。
あなたが講義を受けた理由は実際にあります:それはあなたの問題に対する正しい答えです。これが背景です。おそらく、ライブ環境にいくつかの変更を加えることができます。
まず、ディレクトリはファイルシステムに保存されます。それらをファイルと考えてください。それがまさにそれらであるためです。ディレクトリを反復処理するときは、ディスクからそれらのブロックを読み取る必要があります。各ディレクトリエントリには、ファイル名、アクセス許可、およびそのファイルがディスク上のどこにあるかに関する情報を保持するのに十分なスペースが必要です。
2番目:ディレクトリは内部順序で保存されません(少なくとも、ディレクトリファイルを操作したファイルシステムには保存されません)。 150,000のエントリと2つのサブディレクトリがある場合、それらの2つのサブディレクトリ参照は150,000内のどこにあってもかまいません。あなたはそれらを見つけるために繰り返す必要があります、それを回避する方法はありません。
だから、あなたは大きなディレクトリを避けることができないとしましょう。唯一の現実的なオプションは、ディレクトリファイルを構成するブロックをメモリ内キャッシュに保持して、アクセスするたびにディスクにアクセスしないようにすることです。これは、バックグラウンドスレッドでディレクトリを定期的に繰り返すことで実現できますが、これにより、ディスクに過度の負荷がかかり、他のプロセスに干渉します。または、1回スキャンして、結果を追跡することもできます。
別の方法は、階層化されたディレクトリ構造を作成することです。商用Webサイトを見ると、/ 1/150/15023.htmlのようなURLが表示されます。これは、ディレクトリあたりのファイル数を少なくするためのものです。データベース内のBTreeインデックスと考えてください。
もちろん、その構造を非表示にすることもできます。ファイル名を取得し、それらのファイル名を見つけることができるディレクトリツリーを自動的に生成するファイルシステム抽象化レイヤーを作成できます。
cmd.exe
への砲撃のオーバーヘッドがそれを食い尽くすかどうかはわかりませんが、1つの可能性は次のようになります。
...
Runtime r = Runtime.getRuntime();
Process p = r.exec("cmd.exe /k dir /s/b/ad C:\\folder");
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
for (;;) {
String d = br.readLine();
if (d == null)
break;
System.out.println(d);
}
...
重要な問題は、ループで呼び出されるFile.isDirectory()関数である可能性があります。
File.isDirectory()は非常に遅くなる可能性があります。 NFSが200ファイルディレクトリを処理するのに10秒かかるのを見ました。
File.isDirectory()の呼び出しを確実に防ぐことができれば(たとえば、拡張子のテスト、拡張子なし==ディレクトリ)、パフォーマンスを大幅に向上させることができます。
それ以外の場合は、JNA/JNIを実行するか、これを実行するネイティブスクリプトを作成することをお勧めします。
jCifs ライブラリを使用すると、Windowsネットワーク共有をより効率的に操作できます。他のネットワークファイルシステムでこれを行うライブラリを私は知りません。
150kファイルすべて(またはそれらのかなりの数)に次のような同様の命名規則がある場合は、ハッキングできます。
*.jpg
*Out.txt
実際には、フォルダであるかどうかわからないもののファイルオブジェクトのみを作成します。
たくさんのファイルを列挙するJavaアプリケーションでパフォーマンスをデバッグするときに、同様の質問に遭遇しました。古いアプローチを使用しています
for (File f : new File("C:\\").listFiles()) {
if (f.isDirectory()) {
continue;
}
}
また、各f.isDirectory()は、少なくともNTFSでは非常に遅いネイティブFileSsystemへの呼び出しであるように見えます。 Java7 NIOには追加のAPIがありますが、すべてのメソッドがそこに適しているわけではありません。ここでJMHベンチマーク結果を提供します
Benchmark Mode Cnt Score Error Units
MyBenchmark.dir_listFiles avgt 5 0.437 ? 0.064 s/op
MyBenchmark.path_find avgt 5 0.046 ? 0.001 s/op
MyBenchmark.path_walkTree avgt 5 1.702 ? 0.047 s/op
数値は、このコードの実行に由来します。
Java -jar target/benchmarks.jar -bm avgt -f 1 -wi 5 -i 5 -t 1
static final String testDir = "C:/Sdk/Ide/NetBeans/src/dev/src/";
static final int nCycles = 50;
public static class Counter {
int countOfFiles;
int countOfFolders;
}
@Benchmark
public List<File> dir_listFiles() {
List<File> files = new ArrayList<>(1000);
for( int i = 0; i < nCycles; i++ ) {
File dir = new File(testDir);
files.clear();
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
continue;
}
files.add(f);
}
}
return files;
}
@Benchmark
public List<Path> path_walkTree() throws Exception {
final List<Path> files = new ArrayList<>(1000);
for( int i = 0; i < nCycles; i++ ) {
Path dir = Paths.get(testDir);
files.clear();
Files.walkFileTree(dir, new SimpleFileVisitor<Path> () {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes arg1) throws IOException {
files.add(path);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes arg1)
throws IOException {
return path == dir ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
}
});
}
return files;
}
@Benchmark
public List<Path> path_find() throws Exception {
final List<Path> files = new ArrayList<>(1000);
for( int i = 0; i < nCycles; i++ ) {
Path dir = Paths.get(testDir);
files.clear();
files.addAll(Files.find(dir, 1, (path, attrs)
-> true /*!attrs.isDirectory()*/).collect(Collectors.toList()));
}
return files;
}
oSが「安定」している場合は、試してみてください [〜#〜] jna [〜#〜] :
これらはすべて「ストリーミングAPI」です。検索を開始する前に、150kのリスト/配列を割り当てる必要はありません。私見これはあなたのシナリオで大きな利点です。
http://blogs.Oracle.com/adventures/entry/fast_directory_scanning にも再帰的な並列スキャンがあります。基本的に、兄弟は並行して処理されます。有望なパフォーマンステストもあります。
これは壁を越えた解決策であり、テストはまったくありません。また、シンボリックリンクをサポートするファイルシステムがあるかどうかにも依存します。これはJavaソリューションではありません。問題はファイルシステム/ OSに関連しており、Javaに関連していないと思われます。
ファイル名の頭文字に基づいたサブディレクトリを使用して並列ディレクトリ構造を作成し、実際のファイルにシンボリックリンクすることは可能ですか?イラスト
/symlinks/a/b/cde
リンクします
/realfiles/abcde
(/ realfilesは150,000ファイルが存在する場所です)
このディレクトリ構造を作成して維持する必要がありますが、それが実用的かどうかを判断するのに十分な情報がありません。ただし、上記の場合、非階層(および低速)ディレクトリに高速(er)インデックスが作成されます。
たぶん、C#/ C/C++でディレクトリ検索プログラムを作成し、JNIを使用してJavaにアクセスすることができます。これによりパフォーマンスが向上するかどうかはわかりません。
その場合、いくつかのJNAソリューションを試すことができます-プラットフォームに依存するディレクトリトラバーサー(WindowsではFindFirst、FindNext)で、反復パターンの可能性があります。また、Java 7のファイルシステムサポートははるかに優れており、仕様を確認する価値があります(詳細は覚えていません)。
編集:アイデア:1つのオプションは、ディレクトリリストの速度の遅さをユーザーの目から隠すことです。クライアント側のアプリでは、リストがユーザーの注意をそらすために機能しているときに、アニメーションを使用できます。実際には、リスト以外にアプリケーションが何をするかによって異なります。
JNI、またはデプロイメントが一定であると言う場合は、Windowsでは「dir」を実行するか、* nixesでは「ls」を実行し、適切なフラグを指定してディレクトリのみを一覧表示します(Runtime.exec())