指定したディレクトリ内のすべてのファイルと、そのディレクトリ内のサブディレクトリを一覧表示したい。ディレクトリをリストする必要はありません。
私の現在のコードは以下です。指定されたディレクトリ内のファイルとディレクトリのみをリストするため、正常に動作しません。
どうすれば修正できますか?
final List<Path> files = new ArrayList<>();
Path path = Paths.get("C:\\Users\\Danny\\Documents\\workspace\\Test\\bin\\SomeFiles");
try
{
DirectoryStream<Path> stream;
stream = Files.newDirectoryStream(path);
for (Path entry : stream)
{
files.add(entry);
}
stream.close();
}
catch (IOException e)
{
e.printStackTrace();
}
for (Path entry: files)
{
System.out.println(entry.toString());
}
Java 8はそのための素晴らしい方法を提供します:
Files.walk(path)
このメソッドはStream<Path>
を返します。
次の要素がディレクトリの場合に自分自身を呼び出すメソッドを作成します
void listFiles(Path path) throws IOException {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
for (Path entry : stream) {
if (Files.isDirectory(entry)) {
listFiles(entry);
}
files.add(entry);
}
}
}
FileVisitor を確認してください。
Path path= Paths.get("C:\\Users\\Danny\\Documents\\workspace\\Test\\bin\\SomeFiles");
final List<Path> files=new ArrayList<>();
try {
Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if(!attrs.isDirectory()){
files.add(file);
}
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
関数がそれ自体を再帰的に呼び出したり、メンバー変数であるファイルリストを持たないようにしたい場合は、スタックを使用できます。
private List<Path> listFiles(Path path) throws IOException {
Deque<Path> stack = new ArrayDeque<Path>();
final List<Path> files = new LinkedList<>();
stack.Push(path);
while (!stack.isEmpty()) {
DirectoryStream<Path> stream = Files.newDirectoryStream(stack.pop());
for (Path entry : stream) {
if (Files.isDirectory(entry)) {
stack.Push(entry);
}
else {
files.add(entry);
}
}
stream.close();
}
return files;
}
Rx Javaを使用すると、JDKからのDirectoryStreamの使用にこだわりながら、さまざまな方法で要件を解決できます。
次の組み合わせで目的の効果が得られます。順番に説明します。
アプローチ1。 flatMap()およびdefer()演算子を使用した再帰的アプローチ
アプローチ2。 flatMap()およびfromCallable演算子を使用した再帰的アプローチ
注:flatMap()の使用をconcatMap()に置き換えた場合、ディレクトリツリーナビゲーションは必ず深さ優先検索で行われます(DFS)マナー。 flatMap()では、DFSの効果は保証されません。
アプローチ1:flatMap()およびdefer()を使用
private Observable<Path> recursiveFileSystemNavigation_Using_Defer(Path dir) {
return Observable.<Path>defer(() -> {
//
// try-resource block
//
try(DirectoryStream<Path> children = Files.newDirectoryStream(dir))
{
//This intermediate storage is required because DirectoryStream can't be navigated more than once.
List<Path> subfolders = Observable.<Path>fromIterable(children)
.toList()
.blockingGet();
return Observable.<Path>fromIterable(subfolders)
/* Line X */ .flatMap(p -> !isFolder(p) ? Observable.<Path> just(p) : recursiveFileSystemNavigation_Using_Defer(p), Runtime.getRuntime().availableProcessors());
// /* Line Y */ .concatMap(p -> !isFolder(p) ? Observable.<Path> just(p) : recursiveFileSystemNavigation_Using_Defer(p));
} catch (IOException e) {
/*
This catch block is required even though DirectoryStream is Closeable
resource. Reason is that .close() call on a DirectoryStream throws a
checked exception.
*/
return Observable.<Path>empty();
}
});
}
このアプローチは、指定されたディレクトリの子を見つけ、その子をObservableとして発行します。子がファイルの場合、サブスクライバはすぐに利用できます。それ以外の場合は、flatMap()でLine Xが各サブディレクトリを引数として再帰的に渡すメソッドを呼び出します。そのようなサブディレクトリごとに、flatmapは子を同時に内部的にサブスクライブします。これは、制御する必要がある連鎖反応のようなものです。
したがって、Runtime.getRuntime()。availableProcessors()を使用すると、flatmap()の最大同時実行レベルが設定され、すべてのサブフォルダーに同時にサブスクライブできなくなります。同時実行レベルを設定せずに、フォルダーに1000個の子があった場合に何が起こるか想像してください。
defer()を使用すると、DirectoryStreamの作成が時期尚早になり、サブフォルダーを見つけるための実際のサブスクリプションが作成された場合にのみ作成されるようになります。
最後に、メソッドはObservable <Path>を返します。これにより、クライアントはサブスクライブして、以下に示すように結果に対して有用な処理を実行できます。
//
// Using the defer() based approach
//
recursiveDirNavigation.recursiveFileSystemNavigation_Using_Defer(startingDir)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.from(Executors.newFixedThreadPool(1)))
.subscribe(p -> System.out.println(p.toUri()));
defer()を使用することの短所は、引数関数がチェック済み例外をスローする場合に、チェック済み例外をうまく処理しないことです。したがって、DirectoryStream(Closeableを実装する)はtry-resourceブロックで作成されましたが、DirectoryStreamの自動クローズはそのチェックされた例外をスローするため、IOExceptionをキャッチする必要がありました。 。
Rxベースのスタイルを使用しているとき、エラー処理のためにcatch()ブロックを使用するのは少し奇妙に聞こえます。これは、リアクティブプログラミングではエラーがイベントとして送信されるためです。そのため、イベントなどのエラーを公開する演算子を使用しないでください。
fromCallable()という名前のより良い代替手段がRx Java 2.xに追加されました。
アプローチ2. flatMap()およびfromCallable演算子を使用
このアプローチは、引数としてCallableをとるfromCallable()演算子を使用します。再帰的なアプローチが必要なので、その呼び出し可能オブジェクトから期待される結果は、指定されたフォルダーの子のObservableです。サブスクライバーが利用可能な結果を受け取るようにしたいので、このメソッドからObservableを返す必要があります。 inner callableの結果はObservableの子リストであるため、最終的な効果はObservable of Observablesです。
private Observable<Observable<Path>> recursiveFileSystemNavigation_WithoutExplicitCatchBlock_UsingFromCallable(Path dir) {
/*
* fromCallable() takes a Callable argument. In this case the callbale's return value itself is
* a list of sub-paths therefore the overall return value of this method is Observable<Observable<Path>>
*
* While subscribing the final results, we'd flatten this return value.
*
* Benefit of using fromCallable() is that it elegantly catches the checked exceptions thrown
* during the callable's call and exposes that via onError() operator chain if you need.
*
* Defer() operator does not give that flexibility and you have to explicitly catch and handle appropriately.
*/
return Observable.<Observable<Path>> fromCallable(() -> traverse(dir))
.onErrorReturnItem(Observable.<Path>empty());
}
private Observable<Path> traverse(Path dir) throws IOException {
//
// try-resource block
//
try(DirectoryStream<Path> children = Files.newDirectoryStream(dir))
{
//This intermediate storage is required because DirectoryStream can't be navigated more than once.
List<Path> subfolders = Observable.<Path>fromIterable(children)
.toList()
.blockingGet();
return Observable.<Path>fromIterable(subfolders)
/* Line X */ .flatMap(p -> ( !isFolder(p) ? Observable.<Path> just(p) : recursiveFileSystemNavigation_WithoutExplicitCatchBlock_UsingFromCallable(p).blockingSingle())
,Runtime.getRuntime().availableProcessors());
// /* Line Y */ .concatMap(p -> ( !isFolder(p) ? Observable.<Path> just(p) : recursiveFileSystemNavigation_WithoutExplicitCatchBlock_UsingFromCallable(p).blockingSingle() ));
}
}
サブスクライバーは、次に示すように結果ストリームをフラット化する必要があります。
//
// Using the fromCallable() based approach
//
recursiveDirNavigation.recursiveFileSystemNavigation_WithoutExplicitCatchBlock_UsingFromCallable(startingDir)
.subscribeOn(Schedulers.io())
.flatMap(p -> p)
.observeOn(Schedulers.from(Executors.newFixedThreadPool(1)))
.subscribe(filePath -> System.out.println(filePath.toUri()));
traverse()メソッドでは、なぜ行XがブロッキングGetを使用しているのか
再帰関数はObservable <Observable>を返すが、その行のflatmapはサブスクライブするためにObservableを必要とするため。
両方のアプローチの行YはconcatMap()を使用します
ConmapMap()は、flatmap()による内部サブスクリプション中に並列処理が必要ない場合に快適に使用できるためです。
どちらのアプローチでも、メソッドisFolderの実装は次のようになります。
private boolean isFolder(Path p){
if(p.toFile().isFile()){
return false;
}
return true;
}
Maven座標Java RX 2.
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<version>2.0.3</version>
</dependency>
Javaファイルでインポート
import Java.io.IOException;
import Java.nio.file.DirectoryStream;
import Java.nio.file.Files;
import Java.nio.file.Path;
import Java.nio.file.Paths;
import Java.util.List;
import Java.util.concurrent.Executors;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
これは私が思いついた最短の実装です:
final List<Path> files = new ArrayList<>();
Path path = Paths.get("C:\\Users\\Danny\\Documents\\workspace\\Test\\bin\\SomeFiles");
try {
Files.walk(path).forEach(entry -> list.add(entry));
} catch (IOException e) {
e.printStackTrack();
}
これを試してください..itはすべてのフォルダを走査し、フォルダとファイルの両方を印刷します-
public static void traverseDir(Path path) {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
for (Path entry : stream) {
if (Files.isDirectory(entry)) {
System.out.println("Sub-Folder Name : " + entry.toString());
traverseDir(entry);
} else {
System.out.println("\tFile Name : " + entry.toString());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}