98 votes

Lister tous les fichiers d'un répertoire de manière récursive avec Java

J'ai cette fonction qui imprime le nom de tous les fichiers d'un répertoire de manière récursive. Le problème est que mon code est très lent car il doit accéder à un périphérique réseau distant à chaque itération.

J'ai l'intention de charger d'abord tous les fichiers du répertoire de manière récursive, puis de passer en revue tous les fichiers avec l'expression rationnelle pour filtrer tous les fichiers que je ne veux pas. Quelqu'un a-t-il une meilleure suggestion ?

public static printFnames(String sDir) {
    File[] faFiles = new File(sDir).listFiles();
    for (File file : faFiles) {
        if (file.getName().matches("^(.*?)")) {
            System.out.println(file.getAbsolutePath());
        }
        if (file.isDirectory()) {
            printFnames(file.getAbsolutePath());
        }
    }
}

C'est juste un test, plus tard je ne vais pas utiliser le code comme ça, à la place je vais ajouter le chemin et la date de modification de chaque fichier qui correspond à une regex avancée dans un tableau.

1 votes

... quelle est la question ? Cherchez-vous simplement à valider que ce code fonctionnera ?

0 votes

Non, je sais que ce code fonctionne mais il est très lent et on dirait qu'il est stupide d'accéder au système de fichiers et de récupérer le contenu de chaque sous-répertoire au lieu de tout récupérer en une fois.

1 votes

145voto

skaffman Points 197885

En supposant qu'il s'agisse d'un code de production réel que vous écrirez, je vous suggère d'utiliser la solution qui a déjà été trouvée pour ce type de problème Apache Commons IO et plus particulièrement FileUtils.listFiles() . Il gère les répertoires imbriqués, les filtres (basés sur le nom, le temps de modification, etc.).

Par exemple, pour votre regex :

Collection files = FileUtils.listFiles(
  dir, 
  new RegexFileFilter("^(.*?)"), 
  DirectoryFileFilter.DIRECTORY
);

Ceci recherchera de manière récursive les fichiers correspondant à l'option ^(.*?) regex, renvoyant les résultats sous forme de collection.

Il est important de noter que cela ne sera pas plus rapide que de développer votre propre code, cela revient à faire la même chose - parcourir un système de fichiers en Java est tout simplement lent. La différence est que la version Apache Commons ne contiendra pas de bogues.

0 votes

J'ai regardé là et à partir de là, j'utiliserais commons.apache.org/io/api-release/index.html?org/apache/commons/ pour obtenir tous les fichiers du répertoire et des sous-répertoires et ensuite rechercher les fichiers afin qu'ils correspondent à mon expression rationnelle. Ou est-ce que je me trompe ?

0 votes

Le problème, c'est qu'il faut plus d'une heure pour analyser le dossier et que faire cela à chaque fois que je lance le programme pour vérifier les mises à jour est extrêmement ennuyeux. Serait-il plus rapide d'écrire cette partie du programme en C et le reste en Java et si oui, y aurait-il une différence significative ? Pour l'instant, j'ai modifié le code de la ligne if isdir et ajouté que le répertoire doit également correspondre à une expression rationnelle pour être inclus dans la recherche. Je vois que dans votre exemple il est écrit DirectoryFileFilter.DIRECTORY, je suppose que je pourrais avoir un filtre regex à cet endroit.

1 votes

L'écrire en utilisant des appels natifs le rendrait absolument plus rapide - FindFirstFile/FineNextFile vous permet d'interroger les attributs du fichier sans avoir à faire un appel séparé pour cela - cela peut avoir des implications massives pour les réseaux à latence élevée. L'approche de Java à cet égard est terriblement inefficace.

30voto

Dan Points 1593

Il s'agit d'une méthode récursive très simple pour obtenir tous les fichiers d'un Root donné.

Il utilise la classe Java 7 NIO Path.

private List<String> getFileNames(List<String> fileNames, Path dir) {
    try(DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
        for (Path path : stream) {
            if(path.toFile().isDirectory()) {
                getFileNames(fileNames, path);
            } else {
                fileNames.add(path.toAbsolutePath().toString());
                System.out.println(path.getFileName());
            }
        }
    } catch(IOException e) {
        e.printStackTrace();
    }
    return fileNames;
}

18voto

jboi Points 2810

Avec Java 7, un moyen plus rapide de parcourir une arborescence de répertoires a été introduit avec la fonction Paths y Files fonctionnalité. Ils sont beaucoup plus rapides que les "anciens". File manière.

Ce serait le code pour parcourir et vérifier les noms de chemin avec une expression régulière :

public final void test() throws IOException, InterruptedException {
    final Path rootDir = Paths.get("path to your directory where the walk starts");

    // Walk thru mainDir directory
    Files.walkFileTree(rootDir, new FileVisitor<Path>() {
        // First (minor) speed up. Compile regular expression pattern only one time.
        private Pattern pattern = Pattern.compile("^(.*?)");

        @Override
        public FileVisitResult preVisitDirectory(Path path,
                BasicFileAttributes atts) throws IOException {

            boolean matches = pattern.matcher(path.toString()).matches();

            // TODO: Put here your business logic when matches equals true/false

            return (matches)? FileVisitResult.CONTINUE:FileVisitResult.SKIP_SUBTREE;
        }

        @Override
        public FileVisitResult visitFile(Path path, BasicFileAttributes mainAtts)
                throws IOException {

            boolean matches = pattern.matcher(path.toString()).matches();

            // TODO: Put here your business logic when matches equals true/false

            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path path,
                IOException exc) throws IOException {
            // TODO Auto-generated method stub
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path path, IOException exc)
                throws IOException {
            exc.printStackTrace();

            // If the root directory has failed it makes no sense to continue
            return path.equals(rootDir)? FileVisitResult.TERMINATE:FileVisitResult.CONTINUE;
        }
    });
}

5 votes

Belle réponse :), il y a aussi une classe implémentée appelée "SimpleFileVisitor", si vous n'avez pas besoin de toutes les fonctions implémentées, vous pouvez juste surcharger les fonctions nécessaires.

14voto

RealHowTo Points 13117

Le moyen rapide d'obtenir le contenu d'un répertoire en utilisant Java 7 NIO :

import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.FileSystems;
import java.nio.file.Path;

...

Path dir = FileSystems.getDefault().getPath( filePath );
DirectoryStream<Path> stream = Files.newDirectoryStream( dir );
for (Path path : stream) {
   System.out.println( path.getFileName() );
}
stream.close();

3 votes

Sympa mais ne récupère que les fichiers d'un seul répertoire. Si vous voulez voir tous les sous-répertoires, voyez ma réponse alternative.

3 votes

Files.newDirectoryStream peut lancer une IOException. Je suggère d'envelopper cette ligne dans un try-with-statement Java7 afin que le flux soit toujours fermé pour vous (exception ou non, sans avoir besoin d'un finally ). Voir aussi ici : stackoverflow.com/questions/17739362/

12voto

Kevin Day Points 9446

L'interface de Java pour lire le contenu des dossiers du système de fichiers n'est pas très performante (comme vous l'avez découvert). Le JDK 7 corrige ce problème avec une toute nouvelle interface pour ce genre de choses, qui devrait apporter des performances de niveau natif à ce genre d'opérations.

Le problème principal est que Java fait un appel système natif pour chaque fichier. Sur une interface à faible latence, ce n'est pas un gros problème, mais sur un réseau avec une latence même modérée, cela s'accumule. Si vous établissez le profil de votre algorithme ci-dessus, vous constaterez que la majeure partie du temps est consacrée à l'appel isDirectory() - c'est parce que vous devez effectuer un aller-retour pour chaque appel à isDirectory(). La plupart des systèmes d'exploitation modernes peuvent fournir ce type d'information lorsque la liste des fichiers/dossiers a été demandée à l'origine (par opposition à l'interrogation de chaque chemin de fichier individuel pour ses propriétés).

Si vous ne pouvez pas attendre le JDK7, une stratégie pour résoudre ce problème de latence consiste à opter pour le multithreading et à utiliser un ExecutorService avec un nombre maximum de threads pour effectuer votre récursion. Ce n'est pas génial (vous devez vous occuper du verrouillage de vos structures de données de sortie), mais ce sera beaucoup plus rapide que de le faire en monotâche.

Dans toutes vos discussions sur ce genre de choses, je vous recommande vivement de comparer avec ce que vous pourriez faire de mieux en utilisant du code natif (ou même un script en ligne de commande qui fait à peu près la même chose). Dire qu'il faut une heure pour traverser une structure de réseau ne veut pas dire grand-chose. Nous dire que vous pouvez le faire en natif en 7 secondes, mais que cela prend une heure en Java attirera l'attention des gens.

3 votes

Java 7 est maintenant là, donc un exemple sur la façon de le faire dans Java 7 serait utile. Ou au moins un lien. Ou un nom de classe à rechercher sur Google. - c'est "stackoverflow" et non "cs théorique" après tout ;-) .

3 votes

Voyons voir... Mon premier message date de mars 2010... Nous sommes maintenant en janvier 2012... Et je viens de vérifier l'historique de mon inventaire d'équipement, et je ne me vois pas avoir eu une machine à remonter le temps en mars 2010, donc je pense que je suis probablement justifié de répondre sans donner d'exemple explicite ;-)

4 votes

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X