2010-06-29 14 views
19

Je dois traiter un répertoire d'environ 2 millions de xml à traiter.Comment lister un répertoire de 2 millions de fichiers dans java sans avoir une exception "out of memory"

J'ai déjà résolu le traitement de distribuer le travail entre les machines et les threads en utilisant les files d'attente et tout va bien.

Mais maintenant le gros problème est le goulot d'étranglement de la lecture du répertoire avec les 2 millions de fichiers afin de remplir les files d'attente de manière incrémentielle.

J'ai essayé d'utiliser la méthode File.listFiles(), mais cela me donne une exception Java out of memory: heap space. Des idées?

+1

Désolé, mais ce système d'exploitation ne supporte pas? Vivre en 1950? Je sais de divers outils soufflant, par exemple sur des fenêtres (l'explorateur devenant AWFULLY lent), mais le système de fichiers le soutient. – TomTom

+5

@ TomTom: FAT32 (ancienne, mais en aucun cas 1950 ère, et encore assez commun) a une limite de fichiers par répertoire de 65k. –

+0

Mais en supposant que quelqu'un utilise cela neglegient - sauf pour les périphériques ne le supportant pas, et puis le problème - devinez quoi - ne serait pas "problème de liste des fichiers". – TomTom

Répondre

11

Tout d'abord, avez-vous la possibilité d'utiliser Java 7? Là, vous avez un FileVisitor et le Files.walkFileTree, qui devrait probablement fonctionner dans vos contraintes de mémoire.

Sinon, la seule façon que je peux penser est d'utiliser File.listFiles(FileFilter filter) avec un filtre qui renvoie toujours false (veiller à ce que la gamme complète des fichiers est jamais gardé en mémoire), mais qui attire les fichiers à traiter le long de la manière, et les met peut-être dans une file d'attente producteur/consommateur ou écrit les noms de fichiers sur le disque pour traversée ultérieure.

Vous pouvez également, si vous contrôlez les noms des fichiers, ou si elles sont nommées d'une manière agréable, vous pouvez traiter les fichiers en morceaux à l'aide d'un filtre qui accepte les noms de fichiers sur le formulaire file0000000 - filefile0001000 puis file0001000-filefile0002000 et ainsi sur.

Si les noms sont pas nommé dans une belle façon comme ça, vous pouvez essayer de les filtrer en fonction du code de hachage du nom de fichier, qui est censé être assez uniformément répartis sur l'ensemble des nombres entiers.


Mise à jour: Soupir. Probablement ne fonctionnera pas. Juste eu un coup d'œil à la mise en œuvre ListFiles:

public File[] listFiles(FilenameFilter filter) { 
    String ss[] = list(); 
    if (ss == null) return null; 
    ArrayList v = new ArrayList(); 
    for (int i = 0 ; i < ss.length ; i++) { 
     if ((filter == null) || filter.accept(this, ss[i])) { 
      v.add(new File(ss[i], this)); 
     } 
    } 
    return (File[])(v.toArray(new File[v.size()])); 
} 

il échoueront probablement à la première ligne de toute façon ... Trier décevant. Je crois que votre meilleure option est de mettre les fichiers dans des répertoires différents.

Btw, pourriez-vous donner un exemple d'un nom de fichier? Sont-ils "devinables"? Comme

for (int i = 0; i < 100000; i++) 
    tryToOpen(String.format("file%05d", i)) 
+0

Java 7 n'est pas une option pour le moment. Actuellement j'essaye l'option de filtre. Heureusement, les fichiers ont une hiérarchie écrite dans le nom de fichier. Donc, cette option pourrait fonctionner. – Fgblanch

+1

aioobe effectivement cela n'a pas fonctionné. J'ai trouvé les noms de fichiers sont "guessables" :) donc je vais le faire dans l'autre sens: Générer les noms de fichiers, puis aller dans le dossier et essayer de les atteindre. Merci beaucoup pour votre aide – Fgblanch

1

Au premier coup vous pourriez essayer d'augmenter la mémoire de votre JVM en passant -Xmx1024m par ex.

+0

J'ai l'impression que cela ne résoudra pas le problème, et la JVM ne manquera plus de mémoire * légèrement * plus tard. – Piskvor

+0

@Piskvor Si oui, je suppose qu'il n'y a aucun moyen de résoudre ce problème. Quoi que vous utilisiez pour analyser le système de fichiers OS, vous aurez besoin d'un certain nombre d'octets - avec 2 millions de fichiers cela peut rapidement devenir trop. – InsertNickHere

+0

vous n'avez pas besoin de conserver toutes vos données dans la RAM en même temps. – Piskvor

2

Pourquoi stockez-vous 2 millions de fichiers dans le même répertoire? Je peux imaginer qu'il ralentit l'accès terriblement au niveau de l'OS déjà.

Je voudrais certainement les diviser en sous-répertoires (par exemple par date/heure de création) avant le traitement. Mais si ce n'est pas possible pour une raison quelconque, cela pourrait-il être fait pendant le traitement? Par exemple. déplacez 1000 fichiers mis en file d'attente pour Process1 dans Directory1, un autre 1000 fichiers pour Process2 dans Directory2 etc. Ensuite, chaque processus/thread ne voit que le nombre (limité) de fichiers qui lui sont affectés.

+0

Plonger leur est un problème à part. Je pense à cela aussi aux fonctions bash du système d'exploitation. Il est impossible de le faire pendant le traitement, car l'exception est lorsque vous essayez de lister le répertoire programme. – Fgblanch

0

Veuillez afficher la trace complète de l'exception de MOO pour identifier l'emplacement du goulot d'étranglement, ainsi qu'un programme Java court et complet indiquant le comportement que vous voyez.

Cela est probablement dû au fait que vous collectez l'ensemble des deux millions d'entrées en mémoire et qu'elles ne correspondent pas. Pouvez-vous augmenter l'espace de tas?

8

Utilisation File.list() au lieu de File.listFiles() - les ils ne contiennent pas String objets il retourne consommer moins de mémoire que les objets File, et (plus important encore, en fonction de l'emplacement du répertoire) le nom de chemin complet.

Ensuite, construisez File objets comme nécessaire lors du traitement du résultat. Cependant, cela ne fonctionnera pas non plus pour les grands répertoires arbitrairement. C'est une meilleure idée globale d'organiser vos fichiers dans une hiérarchie de répertoires de sorte qu'aucun répertoire n'a plus de quelques milliers d'entrées.

0

Si les noms de fichiers suivent certaines règles, vous pouvez utiliser File.list(filter) au lieu de File.listFiles pour obtenir des parties gérables de la liste de fichiers.

-3

Essayez ceci, cela fonctionne pour moi, mais je n'avais pas tant de documents ...

File dir = new File("directory"); 
String[] children = dir.list(); 
if (children == null) { 
    //Either dir does not exist or is not a directory 
    System.out.print("Directory doesn't exist\n"); 
} 
else { 
    for (int i=0; i<children.length; i++) { 
    // Get filename of file or directory 
    String filename = children[i]; 
} 
+0

Il est directement ce qui ne fonctionne pas pour Asker, il a beaucoup de fichiers –

9

Si Java 7 est pas une option, ce hack fonctionne (pour UNIX):

Process process = Runtime.getRuntime().exec(new String[]{"ls", "-f", "/path"}); 
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 
String line; 
while (null != (line = reader.readLine())) { 
    if (line.startsWith(".")) 
     continue; 
    System.out.println(line); 
} 

Le paramètre -f accélérer (de man ls):

-f  do not sort, enable -aU, disable -lst 
+1

Ce n'est pas un hack, mais un moyen de traiter l'API Java limitée;) Mais il devrait ajouter le support pour d'autres systèmes d'exploitation, et il serait prima;) –

2

Puisque vous êtes sous Windows, il semble que vous Sh Vous auriez simplement utilisé ProcessBuilder pour démarrer quelque chose comme "cmd/k dir/b target_directory", capturer la sortie de celui-ci, et l'acheminer dans un fichier. Vous pouvez ensuite traiter ce fichier une ligne à la fois, lire les noms de fichiers et les traiter.

Mieux vaut tard que jamais? ;)

5

Si vous pouvez utiliser Java 7, cela peut être fait de cette façon et vous n'aurez pas ces problèmes de mémoire.

Path path = FileSystems.getDefault().getPath("C:\\path\\with\\lots\\of\\files"); 
     Files.walkFileTree(path, new FileVisitor<Path>() { 
      @Override 
      public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 
       return FileVisitResult.CONTINUE; 
      } 

      @Override 
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 
       // here you have the files to process 
       System.out.println(file); 
       return FileVisitResult.CONTINUE; 
      } 

      @Override 
      public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 
       return FileVisitResult.TERMINATE; 
      } 

      @Override 
      public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 
       return FileVisitResult.CONTINUE; 
      } 
     }); 
-1

Vous pouvez utiliser listFiles avec un FilenameFilter spécial. La première fois que FilenameFilter est envoyé à listFiles, il accepte les 1000 premiers fichiers, puis les enregistre comme visité. La prochaine fois que FilenameFilter est envoyé à listFiles, il ignore les 1000 premiers fichiers visités et renvoie les 1000 suivants, et ainsi de suite jusqu'à la fin.

+0

la première ligne listFiles (même avec FilenameFilter) crée un tableau de chaînes - chaque chaîne un nom de fichier dans le répertoire. En outre, souligné par @aioobe. – gjain

3

Vous pouvez le faire avec la bibliothèque Apache FileUtils. Pas de problème de mémoire. J'ai vérifié avec visualvm.

Iterator<File> it = FileUtils.iterateFiles(folder, null, true); 
    while (it.hasNext()) 
    { 
    File fileEntry = (File) it.next(); 
    } 

Espérons que ça aide. En tant que première approche, vous pouvez essayer de modifier certains paramètres de la mémoire JVM, par exemple, en procédant comme suit: bye

+1

FileUtils (coché avec 2.4) utilise en interne File # list(), de sorte que le même problème avec les gros répertoires apparaîtra. Notez que #iterateFiles() renvoie juste le .iterator() du résultat de #listFiles(). – ankon

0

Augmenter la taille du tas comme il a été suggéré ou même utiliser l'option AggressiveHeap. Compte tenu de la grande quantité de fichiers, cela peut ne pas aider, alors je suggère de contourner le problème. Créer plusieurs fichiers avec des noms de fichiers dans chaque, disons 500k noms de fichiers par fichier et lire à partir d'eux.

0

J'ai rencontré le même problème lorsque j'ai développé une application de balayage de logiciels malveillants.Ma solution est d'exécuter la commande shell pour lister tous les fichiers. Il est plus rapide que les méthodes récursives de parcourir les dossiers par dossiers.

En savoir plus sur commande shell ici: http://adbshell.com/commands/adb-shell-ls

 Process process = Runtime.getRuntime().exec("ls -R /"); 
     BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); 

     //TODO: Read the stream to get a list of file path. 
0

Cela nécessite également Java 7, mais il est plus simple que la Files.walkFileTree réponse si vous voulez juste afficher le contenu d'un répertoire et pas marcher l'arbre entier:

Path dir = Paths.get("/some/directory"); 
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { 
    for (Path path : stream) { 
     handleFile(path.toFile()); 
    } 
} catch (IOException e) { 
    handleException(e); 
} 

La mise en œuvre de DirectoryStream est spécifique à la plateforme et appelle jamais File.list ou quelque chose comme ça, au lieu d'utiliser les appels système Unix ou Windows qui itérer sur un répertoire un entrée à la fois.