Comment récupérer rapidement une liste de répertoires en Java?

supposons un programme très simple qui liste tous les sous-répertoires d'un répertoire donné. Son assez simple? Sauf que la seule façon de lister tous les sous-répertoires en Java est d'utiliser FilenameFilter combiné avec le fichier .liste() .

cela fonctionne pour le cas trivial, mais quand le dossier a dire 150.000 fichiers et 2 sous-dossiers, il est stupide d'attendre là pendant 45 secondes itérant à travers tous les fichiers et les tests pour fichier.isDirectory (). Est-il un meilleur moyen de la liste des sous-répertoires??


PS. Désolé, s'il vous plaît enregistrer les conférences sur le fait d'avoir trop de fichiers dans le même répertoire. Notre environnement live a ceci comme une partie de l'exigence.

18
demandé sur zmarties 2009-06-24 00:26:46

13 réponses

comme nous l'avons déjà mentionné, il s'agit essentiellement d'un problème de matériel. L'accès disque est toujours lent, et la plupart des systèmes de fichiers ne sont pas vraiment conçus pour gérer des répertoires avec autant de fichiers.

si, pour une raison quelconque, vous devez stocker tous les fichiers dans le même répertoire, Je pense que vous devrez gérer votre propre cache. Cela pourrait être fait en utilisant une base de données locale telle que sqlite, HeidiSQL ou HSQL. Si vous voulez des performances extrêmes, utilisez un Java TreeSet et mettez-le en cache en mémoire. Cela signifie à tout le moins que vous aurez à lire le répertoire moins souvent, et il pourrait éventuellement être fait en arrière-plan. Vous pouvez réduire encore davantage le besoin de rafraîchir la liste en utilisant votre API de notification de mise à jour de fichiers natifs (inotify sur linux) pour vous abonner aux modifications du répertoire.

cela ne semble pas être possible pour vous, mais j'ai déjà résolu un problème similaire en" hashant " les fichiers dans des sous-répertoires. Dans mon cas, le défi était de stocker un quelques millions d'images avec des identifiants numériques. J'ai construit la structure du répertoire comme suit:

images/[id - (id % 1000000)]/[id - (id % 1000)]/[id].jpg

cela a bien fonctionné pour nous, et c'est la solution que je recommande. Vous pourriez faire quelque chose de similaire aux noms de fichiers Alphanumériques en prenant simplement les deux premières lettres du nom de fichier, puis les deux suivantes. J'ai fait ça aussi bien une fois, et ça a fait le travail aussi.

10
répondu Emil H 2009-06-23 21:42:49

connaissez-vous la liste des noms de sous-répertoires possibles? Si c'est le cas, utilisez une boucle sur tous les noms possibles et vérifiez l'existence du répertoire.

sinon, vous ne pouvez pas obtenir seulement les noms de répertoire dans la plupart des os sous-jacents (par exemple, dans Unix, la liste des répertoires est simplement la lecture du contenu du fichier "répertoire", il n'y a donc aucun moyen de trouver "juste des répertoires" rapidement sans lister tous les fichiers).

Toutefois, dans NIO.2 en Java7 (voir http://java.sun.com/developer/technicalArticles/javase/nio/#3 ), il y a un moyen d'avoir une liste de répertoires de streaming pour ne pas avoir un tableau complet d'éléments de fichiers encombrant votre mémoire/réseau.

7
répondu DVK 2009-06-23 20:46:51

il y a en fait une raison pour laquelle vous avez eu les cours: c'est la bonne réponse à votre problème. Voici le contexte, afin que peut-être vous pouvez faire quelques changements dans votre environnement de vie.

D'abord: les répertoires sont stockés sur le système de fichiers; pensez-y comme des fichiers, parce que c'est exactement ce qu'ils sont. Lorsque vous parcourir le répertoire, vous devez lire ces blocs à partir du disque. Chaque entrée de répertoire aura besoin de suffisamment d'espace pour contenir le nom du fichier, et autorisations, et des informations sur l'endroit où le fichier se trouve sur le disque.

Second: les répertoires ne sont pas stockés avec un ordre interne (du moins pas dans les systèmes de fichiers où j'ai travaillé avec des fichiers répertoires). Si vous avez 150 000 entrées et 2 sous-répertoires, ces 2 références de sous-répertoire pourraient être n'importe où dans les 150 000. Vous devez itérer pour les trouver, il n'y a aucun moyen de contourner cela.

Donc, disons que vous ne pouvez pas éviter le grand répertoire. Votre seule option réelle est d'essayer de garder les blocs comprenant le fichier répertoire dans la mémoire cache, de sorte que vous ne frappez pas le disque à chaque fois que vous y accédez. Pour cela, vous pouvez régulièrement de parcourir le répertoire dans un thread d'arrière-plan -- mais cela va provoquer une charge excessive sur vos disques, et interférer avec d'autres processus. Alternativement, vous pouvez scanner une fois et garder la trace des résultats.

l'alternative est de créer une structure de répertoire hiérarchisée. Si vous regardez les sites Web commerciaux, vous verrez des URLs comme /1/150/15023.html -- ceci est destiné à garder petit le nombre de fichiers par répertoire. Pensez - y comme un index BTree dans une base de données.

bien sûr, vous pouvez cacher cette structure: vous pouvez créer une couche d'abstraction du système de fichiers qui prend les noms de fichiers et génère automatiquement l'arborescence du répertoire où ces noms de fichiers peuvent être trouvés.

5
répondu kdgregory 2009-06-23 20:44:12

je ne sais pas si les frais généraux de bombardements cmd.exe mangerait, mais une possibilité serait quelque chose comme ceci:

...
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);
}
...
  • /s signifie sous-répertoires de recherche
  • ad signifie retourner uniquement les répertoires
  • /b signifie que le retour le chemin complet depuis la racine
4
répondu lavinio 2009-06-23 20:59:38

vous pourriez le hacker si les fichiers 150k tous (ou un nombre significatif d'entre eux) avaient une convention d'appellation similaire comme:

*.jpg
*Out.txt

et ne créent réellement des objets de fichier que pour ceux que vous n'êtes pas sûr d'être un dossier.

3
répondu Hardwareguy 2009-06-23 20:31:32

le problème clé pourrait être le fichier.isdirectory () fonction appelée dans une boucle.

fichier.isDirectory () peut être extrêmement lent. J'ai vu NFS prendre 10 secondes pour traiter le répertoire de 200 fichiers.

si vous pouvez par tous les moyens empêcher le fichier.appels isDirectory () (par exemple test pour extension, pas d'extension == répertoire), vous pouvez améliorer les performances de manière drastique.

sinon je suggérerais de faire JNA/JNI / écrire un script natif qui ne cela pour vous.

la bibliothèque jCifs vous permet de manipuler les partages réseau windows plus efficacement. Je ne suis pas au courant d'une bibliothèque qui ferait cela pour d'autres systèmes de fichiers réseau.

3
répondu Roman Zenka 2009-11-06 15:57:57

si votre système D'exploitation est "stable", essayez JNA :

ce sont tous des"API de streaming". Ils ne vous forcent pas à allouer une liste ou un tableau de 150k avant de commencer la recherche. IMHO c'est un grand avantage dans votre scénario.

2
répondu dfa 2009-06-23 21:22:55

Voici une solution hors du mur, et sans aucun test du tout. Cela dépend aussi d'avoir un système de fichiers qui supporte les liens symboliques. Ce n'est pas une solution Java. Je soupçonne que votre problème est lié au système de fichiers / OS, et non à Java.

est-il possible de créer une structure de répertoire parallèle, avec des sous-répertoires basés sur les lettres initiales des noms de fichiers, et ensuite symboliquement lié aux fichiers réels ? Une illustration

/symlinks/a/b/cde

serait lié à

/realfiles/abcde

(où /realfiles est l'endroit où vos de 150 000 fichiers)

vous devez créer et maintenir cette structure de répertoire, et je n'ai pas assez d'informations pour déterminer si c'est pratique. Mais ce qui précède créerait un(er) index rapide dans votre répertoire non-hiérarchique (et lent).

1
répondu Brian Agnew 2009-06-23 21:31:55

il y a aussi un balayage parallèle récursif à http://blogs.oracle.com/adventures/entry/fast_directory_scanning . Essentiellement, les frères et sœurs sont traités en parallèle. Il y a aussi des tests de performance encourageants.

1
répondu dfa 2012-07-31 20:08:23

j'ai rencontré la même question lors du débogage de performances dans une application Java énumérant beaucoup de fichiers. C'est à l'aide de l'ancienne approche

for (File f : new File("C:\").listFiles()) {
    if (f.isDirectory()) {
        continue;
    }        
}

et il apparaît que chaque F. isDirectory () est l'appel dans le système de fichiers natif qui, au moins sur NTFS, est très lent. Java7 NIO a une API supplémentaire, mais toutes les méthodes ne sont pas bonnes. Je vais juste fournir JMH benchmark résultat ici

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

nombre provenant de l'exécution de ce code:

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;
}
1
répondu Xtra Coder 2016-09-14 17:20:03

peut-être que vous pourriez écrire un programme de recherche de répertoire en C#/C/C++ et utiliser JNI pour L'obtenir en Java. Je ne sais pas si cela améliorerait la performance ou non.

0
répondu Nick 2009-06-23 20:37:17

Eh bien, soit JNI, soit, si vous dites que votre déploiement est constant, Lancez juste "dir" sur Windows ou "ls" sur *nixes, avec les drapeaux appropriés pour n'énumérer que les répertoires (Runtime.exec ())

0
répondu Yoni Roit 2009-06-23 20:44:17

dans ce cas, vous pouvez essayer une solution JNA - un gestionnaire de répertoires dépendant de la plate-forme (FindFirst, FindNext sous Windows) avec la possibilité d'un motif d'itération. En outre Java 7 aura beaucoup mieux le soutien de système de dossier, la peine de vérifier les spécifications (Je ne me souviens pas de détails).

Edit: Une idée: une option est de cacher la lenteur de la liste des répertoires les yeux de l'utilisateur. Dans une application côté client, vous pourriez utiliser animation pendant que le listing travaille pour distraire l'utilisateur. En fait dépend de ce que votre application fait à côté de la liste.

0
répondu akarnokd 2009-06-23 21:05:54