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


85

J'ai cette fonction qui imprime le nom de tous les fichiers dans 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.

Mon plan est de commencer par charger tous les fichiers du répertoire de manière récursive, puis de parcourir tous les fichiers avec l'expression régulière 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());
    }
  }
}

Ceci est juste un test plus tard, je n'utiliserai pas le code comme celui-ci, à la place je vais ajouter le chemin et la date de modification de chaque fichier qui correspond à une expression régulière avancée à un tableau.


1
... quelle est la question? Cherchez-vous simplement à valider que ce code fonctionnera?
Richard JP Le Guen

Non, je sais que ce code fonctionne mais il est très lent et il semble stupide d'accéder au système de fichiers et d'obtenir le contenu de chaque sous-répertoire au lieu de tout obtenir en même temps.
Hultner

Réponses:


134

En supposant qu'il s'agisse du code de production que vous allez écrire, je suggère d'utiliser la solution à ce genre de chose qui a déjà été résolue - Apache Commons IO , en particulier FileUtils.listFiles(). Il gère les répertoires imbriqués, les filtres (basés sur le nom, l'heure de modification, etc.).

Par exemple, pour votre regex:

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

Cela recherchera récursivement les fichiers correspondant à l' ^(.*?)expression régulière, renvoyant les résultats sous forme de collection.

Il convient de noter que ce ne sera pas plus rapide que de rouler votre propre code, cela fait la même chose - parcourir un système de fichiers en Java est juste lent. La différence est que la version Apache Commons ne contient aucun bogue.


J'ai regardé là-bas 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, puis chercher dans les fichiers afin que ils correspondent à mon regex. Ou ai-je tort?
Hultner

Ouais problème, il faut plus d'une heure pour analyser le dossier et le faire chaque fois que je lance le programme pour vérifier les mises à jour est extrêmement ennuyeux. Serait-ce plus rapide si j'écrivais 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 changé le code sur la ligne if isdir et ajouté de sorte que le répertoire doive également correspondre à une expression régulière pour être inclus dans la recherche. Je vois que dans votre exemple, il est dit DirectoryFileFilter.DIRECTORY, je suppose que je pourrais avoir un filtre regex là-bas.
Hultner

1
l'écrire à l'aide d'appels natifs le rendrait absolument plus rapide - FindFirstFile / FineNextFile vous permet d'interroger les attributs du fichier sans avoir à faire un appel séparé - cela peut avoir des implications massives pour les réseaux à latence plus élevée. L'approche de Java à ce sujet est horriblement inefficace.
Kevin Day

5
@ hanzallah-afgan: La question et la réponse datent de plus de 5 ans. Il y a eu deux versions majeures de Java au cours de cette période, vous ne voudrez peut-être pas étudier de nouvelles fonctionnalités telles que Java 7 NIO.
Hultner

4
N'utilisez FileUtils que si vous connaissez et acceptez la performance hit: github.com/brettryan/io-recurse-tests . Les alternatives natives de Java8 permettent une notation concise et plus efficace, par exemple:Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList())
ccpizza

64

En Java 8, c'est un via 1-liner Files.find()avec une profondeur arbitrairement grande (par exemple 999) et BasicFileAttributesdeisRegularFile()

public static printFnames(String sDir) {
    Files.find(Paths.get(sDir), 999, (p, bfa) -> bfa.isRegularFile()).forEach(System.out::println);
}

Pour ajouter plus de filtrage, améliorez le lambda, par exemple tous les fichiers jpg modifiés au cours des dernières 24 heures:

(p, bfa) -> bfa.isRegularFile()
  && p.getFileName().toString().matches(".*\\.jpg")
  && bfa.lastModifiedTime().toMillis() > System.currentMillis() - 86400000

3
Je suggère de toujours utiliser les méthodes Files qui retournent Stream dans des blocs try-with-resources: sinon vous garderez la ressource ouverte
riccardo.tasso

Les opérations du terminal n'appellent-elles pas se fermer en direct?
Dragas

@Dragas oui. Mon consommateur n'est qu'un simple exemple; dans la vraie vie, vous feriez quelque chose de plus utile.
Bohème

27

Il s'agit d'une méthode récursive très simple pour récupérer tous les fichiers d'une racine donnée.

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;
} 

18

Avec Java 7, un moyen plus rapide de parcourir une arborescence de répertoires a été introduit avec la fonctionnalité Pathset Files. Ils sont beaucoup plus rapides que la Fileméthode «ancienne» .

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
Bonne 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 simplement remplacer les fonctions nécessaires.
GalDude33

13

Le moyen rapide d'obtenir le contenu d'un répertoire à l'aide de 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
Bien mais n'obtient que des fichiers pour un répertoire. Si vous voulez voir tous les sous-répertoires, voyez ma réponse alternative.
Dan

3
Files.newDirectoryStreampeut lancer une IOException. Je suggère d'encapsuler cette ligne dans une instruction Java7 try-with-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/…
Greg

12

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). JDK 7 corrige cela avec une interface complètement nouvelle pour ce genre de chose, qui devrait apporter des performances de niveau natif à ce type d'opérations.

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

Si vous ne pouvez pas attendre JDK7, une stratégie pour résoudre cette latence est de passer au multi-thread et d'utiliser un ExecutorService avec un nombre maximum de threads pour effectuer votre récursivité. Ce n'est pas génial (vous devez gérer le verrouillage de vos structures de données de sortie), mais ce sera beaucoup plus rapide que de faire ce thread unique.

Dans toutes vos discussions sur ce genre de chose, je vous recommande fortement de comparer avec le mieux que vous puissiez faire en utilisant du code natif (ou même un script de 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 vraiment dire grand-chose. Nous dire que vous pouvez le faire nativement en 7 secondes, mais que cela prend une heure en Java, cela attirera l'attention des gens.


3
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 du «stackoverflow» et non du «cs théorique» après tout ;-).
Martin

3
voyons bien ... Mon message d'origine était en 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 ;-)
Kevin Day


7

cela fonctionnera très bien ... et son récursif

File root = new File("ROOT PATH");
for ( File file : root.listFiles())
{
    getFilesRecursive(file);
}


private static void getFilesRecursive(File pFile)
{
    for(File files : pFile.listFiles())
    {
        if(files.isDirectory())
        {
            getFilesRecursive(files);
        }
        else
        {
            // do your thing 
            // you can either save in HashMap and use it as
            // per your requirement
        }
    }
}

1
Bonne réponse si vous voulez quelque chose qui fonctionne avec java <7.
ssimm

3

J'aime personnellement cette version de FileUtils. Voici un exemple qui trouve tous les mp3 ou flacs dans un répertoire ou l'un de ses sous-répertoires:

String[] types = {"mp3", "flac"};
Collection<File> files2 = FileUtils.listFiles(/path/to/your/dir, types , true);

3

Cela fonctionnera bien

public void displayAll(File path){      
    if(path.isFile()){
        System.out.println(path.getName());
    }else{
        System.out.println(path.getName());         
        File files[] = path.listFiles();
        for(File dirOrFile: files){
            displayAll(dirOrFile);
        }
    }
}


Bienvenue chez StackOverflow Mam's, pourriez-vous clarifier en quoi votre réponse est une amélioration ou une alternative aux nombreuses réponses existantes?
Lilienthal

1

Cette fonction listera probablement tout le nom de fichier et son chemin depuis son répertoire et ses sous-répertoires.

public void listFile(String pathname) {
    File f = new File(pathname);
    File[] listfiles = f.listFiles();
    for (int i = 0; i < listfiles.length; i++) {
        if (listfiles[i].isDirectory()) {
            File[] internalFile = listfiles[i].listFiles();
            for (int j = 0; j < internalFile.length; j++) {
                System.out.println(internalFile[j]);
                if (internalFile[j].isDirectory()) {
                    String name = internalFile[j].getAbsolutePath();
                    listFile(name);
                }

            }
        } else {
            System.out.println(listfiles[i]);
        }

    }

}

1
Cet exemple ne prend pas en compte le fait que la méthode listFiles () peut et retournera null. docs.oracle.com/javase/7/docs/api/java/io/File.html#listFiles ()
Matt Jones

1

Java 8

public static void main(String[] args) throws IOException {

        Path start = Paths.get("C:\\data\\");
        try (Stream<Path> stream = Files.walk(start, Integer.MAX_VALUE)) {
            List<String> collect = stream
                .map(String::valueOf)
                .sorted()
                .collect(Collectors.toList());

            collect.forEach(System.out::println);
        }


    }

0

on a l'impression qu'il est stupide d'accéder au système de fichiers et d'obtenir le contenu de chaque sous-répertoire au lieu de tout obtenir en même temps.

Votre sentiment est faux. C'est ainsi que fonctionnent les systèmes de fichiers. Il n'y a pas de moyen plus rapide (sauf lorsque vous devez le faire à plusieurs reprises ou pour différents modèles, vous pouvez mettre en cache tous les chemins de fichiers en mémoire, mais vous devez ensuite gérer l'invalidation du cache, c'est-à-dire ce qui se passe lorsque des fichiers sont ajoutés / supprimés / renommés pendant l'application s'exécute).


Le fait est que je veux charger tous les fichiers d'un certain type avec un certain format de nom dans une bibliothèque qui est présentée à l'utilisateur et à chaque fois que l'application est lancée, la bibliothèque est censée être mise à jour, mais la mise à jour de la bibliothèque prend une éternité. La seule solution que j'ai est d'exécuter la mise à jour en arrière-plan, mais c'est toujours ennuyeux que cela prenne si longtemps avant que tous les nouveaux fichiers soient chargés. Il doit y avoir une meilleure façon de le faire. Ou au moins une meilleure façon de mettre à jour la base de données. Cela semble stupide pour lui de parcourir tous les fichiers qu'il a déjà traversés une fois. Existe-t-il un moyen de ne trouver que les mises à jour rapidement.
Hultner

@Hultner: Java 7 inclura une fonction pour être averti des mises à jour du système de fichiers, mais cela ne fonctionnerait toujours que pendant que l'application est en cours d'exécution, donc à moins que vous ne souhaitiez qu'un service d'arrière-plan soit exécuté en permanence, cela ne vous aiderait pas. Il peut y avoir des problèmes particuliers avec les partages réseau, comme le décrit Kevin, mais tant que vous dépendez de l'analyse de toute l'arborescence de répertoires, il n'y a vraiment pas de meilleur moyen.
Michael Borgwardt

Vous pourriez peut-être créer des fichiers d'index. S'il existe un moyen de vérifier la taille du répertoire, vous pouvez simplement rechercher de nouveaux fichiers lorsque la taille change.
James P.

@James: il n'y a aucun moyen de vérifier la taille du répertoire. La taille d'un répertoire est obtenue en obtenant la taille de chaque fichier et en les additionnant, dans tous les systèmes de fichiers dont j'ai connaissance. En fait, la question "quelle est la taille de ce répertoire?" n'a même pas nécessairement de sens si vous considérez les liens physiques.
Michael Borgwardt

Tu as raison. J'ai toujours le sentiment que la mise en cache et / ou la prise d'empreintes pourraient accélérer le processus.
James P.

0

Juste pour que vous sachiez que isDirectory () est une méthode assez lente. Je trouve cela assez lent dans mon navigateur de fichiers. Je vais chercher dans une bibliothèque pour la remplacer par du code natif.


0

Le moyen le plus efficace que j'ai trouvé pour traiter des millions de dossiers et de fichiers est de capturer la liste des répertoires via la commande DOS dans un fichier et de l'analyser. Une fois que vous avez analysé les données, vous pouvez effectuer des analyses et calculer des statistiques.


0
import java.io.*;

public class MultiFolderReading {

public void checkNoOfFiles (String filename) throws IOException {

    File dir=new File(filename);
    File files[]=dir.listFiles();//files array stores the list of files

 for(int i=0;i<files.length;i++)
    {
        if(files[i].isFile()) //check whether files[i] is file or directory
        {
            System.out.println("File::"+files[i].getName());
            System.out.println();

        }
        else if(files[i].isDirectory())
        {
            System.out.println("Directory::"+files[i].getName());
            System.out.println();
            checkNoOfFiles(files[i].getAbsolutePath());
        }
    }
}

public static void main(String[] args) throws IOException {

    MultiFolderReading mf=new MultiFolderReading();
    String str="E:\\file"; 
    mf.checkNoOfFiles(str);
   }
}

Veuillez également ajouter quelques explications.
d4Rk

0

Dans Guava, vous n'avez pas à attendre qu'une collection vous soit renvoyée, mais vous pouvez en fait parcourir les fichiers. Il est facile d'imaginer une IDoSomethingWithThisFileinterface dans la signature de la fonction ci-dessous:

public static void collectFilesInDir(File dir) {
    TreeTraverser<File> traverser = Files.fileTreeTraverser();
    FluentIterable<File> filesInPostOrder = traverser.preOrderTraversal(dir);
    for (File f: filesInPostOrder)
        System.out.printf("File: %s\n", f.getPath());
}

TreeTraverser vous permet également de choisir entre différents styles de parcours.


0
public class GetFilesRecursive {
    public static List <String> getFilesRecursively(File dir){
        List <String> ls = new ArrayList<String>();
        for (File fObj : dir.listFiles()) {
            if(fObj.isDirectory()) {
                ls.add(String.valueOf(fObj));
                ls.addAll(getFilesRecursively(fObj));               
            } else {
                ls.add(String.valueOf(fObj));       
            }
        }

        return ls;
    }
    public static List <String> getListOfFiles(String fullPathDir) {
        List <String> ls = new ArrayList<String> ();
        File f = new File(fullPathDir);
        if (f.exists()) {
            if(f.isDirectory()) {
                ls.add(String.valueOf(f));
                ls.addAll(getFilesRecursively(f));
            }
        } else {
            ls.add(fullPathDir);
        }
        return ls;
    }

    public static void main(String[] args) {
        List <String> ls = getListOfFiles("/Users/srinivasab/Documents");
        for (String file:ls) {
            System.out.println(file);
        }
        System.out.println(ls.size());
    }
}

0

Un autre code optimisé

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class GetFilesRecursive {
    public static List <String> getFilesRecursively(File dir){
        List <String> ls = new ArrayList<String>();
        if (dir.isDirectory())
            for (File fObj : dir.listFiles()) {
                if(fObj.isDirectory()) {
                    ls.add(String.valueOf(fObj));
                    ls.addAll(getFilesRecursively(fObj));               
                } else {
                    ls.add(String.valueOf(fObj));       
                }
            }
        else
            ls.add(String.valueOf(dir));

        return ls;
    }

    public static void main(String[] args) {
        List <String> ls = getFilesRecursively(new File("/Users/srinivasab/Documents"));
        for (String file:ls) {
            System.out.println(file);
        }
        System.out.println(ls.size());
    }
}

Pouvez-vous étendre votre réponse avec des explications plus détaillées? Cela sera très utile pour la compréhension. Merci!
vezunchik

0

Un autre exemple de liste de fichiers et de répertoires à l'aide de Java 8 filter

public static void main(String[] args) {

System.out.println("Files!!");
        try {
            Files.walk(Paths.get("."))
                    .filter(Files::isRegularFile)
                    .filter(c ->
                            c.getFileName().toString().substring(c.getFileName().toString().length()-4).contains(".jpg")
                            ||
                            c.getFileName().toString().substring(c.getFileName().toString().length()-5).contains(".jpeg")
                    )
                    .forEach(System.out::println);

        } catch (IOException e) {
        System.out.println("No jpeg or jpg files");
        }

        System.out.println("\nDirectories!!\n");
        try {
            Files.walk(Paths.get("."))
                    .filter(Files::isDirectory)
                    .forEach(System.out::println);

        } catch (IOException e) {
            System.out.println("No Jpeg files");
        }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.