Je souhaite répertorier tous les fichiers du répertoire spécifié et des sous-répertoires de ce répertoire. Aucun répertoire ne devrait être répertorié.
Mon code actuel est ci-dessous. Il ne fonctionne pas correctement car il ne répertorie que les fichiers et les répertoires du répertoire spécifié.
Comment puis-je réparer cela?
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 fournit un bon moyen pour cela:
Files.walk(path)
Cette méthode retourne Stream<Path>
.
Faire une méthode qui s'appellera si un prochain élément est directory
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);
}
}
}
Vérifiez FileVisitor , très soigné.
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();
}
Si vous voulez éviter que la fonction s'appelle de manière récursive et que la liste de fichiers soit une variable membre, vous pouvez utiliser une pile:
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;
}
En utilisant Java Rx, l'exigence peut être résolue de plusieurs manières tout en restant fidèle à l'utilisation de DirectoryStream à partir de JDK.
Les combinaisons suivantes vous donneront l’effet désiré, je les expliquerais en séquence:
approche 1. Une approche récursive utilisant les opérateurs flatMap () et defer ()
approche 2. Une approche récursive utilisant flatMap () et les opérateurs fromCallable
Remarque: Si vous remplacez l'utilisation de flatMap () par concatMap (), la navigation dans l'arborescence des répertoires aura nécessairement lieu dans une recherche en profondeur d'abord (DFS) manière. Avec flatMap (), l’effet DFS n’est pas garanti.
approche 1: utilisation de flatMap () et 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();
}
});
}
Cette approche consiste à rechercher les enfants d'un répertoire donné, puis à les émettre en tant qu'observables. Si un enfant est un fichier, il sera immédiatement disponible pour un abonné, sinon flatMap () sur Ligne X invoquera la méthode en passant récursivement chaque sous-répertoire en argument. Pour chaque sous-répertoire, flatmap souscrira en interne à ses enfants en même temps. Cela ressemble à une réaction en chaîne qui doit être contrôlée.
Par conséquent, l'utilisation de Runtime.getRuntime (). AvailableProcessors () définit le niveau de simultanéité maximale pour flatmap () et l'empêche de s'abonner à tous les sous-dossiers en même temps. Sans définir le niveau de concurrence, imaginez ce qui se passera lorsqu'un dossier aura 1000 enfants.
L'utilisation de defer () empêche la création prématurée d'un DirectoryStream et garantit que cela ne se produira que lorsqu'un véritable abonnement pour trouver ses sous-dossiers est créé.
Enfin, la méthode retourne un Observable <Path> afin qu'un client puisse s'abonner et faire quelque chose d'utile avec les résultats, comme indiqué ci-dessous:
//
// 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()));
Le désavantage d'utiliser defer () est qu'il ne traite pas les exceptions vérifiées correctement si sa fonction argument lance une exception vérifiée. Par conséquent, même si DirectoryStream (qui implémente Closeable) a été créé dans un bloc try-resource, nous avons quand même dû intercepter IOException car la fermeture automatique d'un DirectoryStream lève l'exception vérifiée. .
Lors de l'utilisation d'un style basé sur Rx, l'utilisation de blocs catch () pour la gestion des erreurs semble un peu étrange car des erreurs sont envoyées sous forme d'événements dans la programmation réactive. Alors pourquoi ne pas utiliser un opérateur qui expose de telles erreurs en tant qu’événements.
Une meilleure alternative appelée fromCallable () a été ajoutée dans Rx Java 2.x. La 2ème approche en montre l'utilisation.
Approche 2. Utilisation des opérateurs flatMap () et fromCallable
Cette approche utilise l'opérateur fromCallable () qui prend un argument appelable. Puisque nous voulons une approche récursive, le résultat attendu de cet appelable est un observable d'enfants du dossier donné. Puisque nous voulons qu'un abonné reçoive les résultats lorsqu'ils sont disponibles, nous devons renvoyer un observable à partir de cette méthode. Le résultat de inner callable étant une liste d'enfants observable, l'effet net est un observable d'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() ));
}
}
Un abonné devra alors aplatir le flux de résultats comme indiqué ci-dessous:
//
// 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()));
Dans la méthode traverse (), pourquoi la ligne X utilise-t-elle le blocage de Get
Parce que la fonction récursive renvoie un observable <Observable>, mais un flatmap situé sur cette ligne nécessite un observable auquel souscrire.
La ligne Y des deux approches utilise concatMap ()
Parce que concatMap () peut être utilisé confortablement si nous ne voulons pas de parallélisme lors des souscriptions internes faites par flatmap ().
Dans les deux approches, l’implémentation de la méthode isFolder se présente comme suit:
private boolean isFolder(Path p){
if(p.toFile().isFile()){
return false;
}
return true;
}
coordonnées Maven pour Java RX 2.
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<version>2.0.3</version>
</dependency>
Importe dans 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;
Ceci est la mise en œuvre la plus courte que je propose:
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();
}
Essayez ceci ..il parcourt tous les dossiers et affiche les deux dossiers ainsi que les fichiers: -
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();
}
}