2015-11-10 5 views
7

Ich habe (in Java) einen ziemlich direkten Iterator implementiert, um die Namen der Dateien in einer rekursiven Verzeichnisstruktur zurückzugeben, und nach etwa 2300 Dateien fehlgeschlagen "Zu viele offene Dateien im System" (der Fehler war eigentlich der Versuch, eine Klasse zu laden, aber ich nehme an, dass die Verzeichnisliste der Schuldige war)."Zu viele geöffnete Dateien im System" Fehler beim Auflisten einer rekursiven Verzeichnisstruktur

Die vom Iterator verwaltete Datenstruktur ist ein Stack, der den Inhalt der Verzeichnisse enthält, die auf jeder Ebene geöffnet sind.

Die eigentliche Logik ist recht einfach:

private static class DirectoryIterator implements Iterator<String> { 

     private Stack<File[]> directories; 
     private FilenameFilter filter; 
     private Stack<Integer> positions = new Stack<Integer>(); 
     private boolean recurse; 
     private String next = null; 

     public DirectoryIterator(Stack<File[]> directories, boolean recurse, FilenameFilter filter) { 
      this.directories = directories; 
      this.recurse = recurse; 
      this.filter = filter; 
      positions.push(0); 
      advance(); 
     } 

     public boolean hasNext() { 
      return next != null; 
     } 

     public String next() { 
      String s = next; 
      advance(); 
      return s; 
     } 

     public void remove() { 
      throw new UnsupportedOperationException(); 
     } 

     private void advance() { 
      if (directories.isEmpty()) { 
       next = null; 
      } else { 
       File[] files = directories.peek(); 
       while (positions.peek() >= files.length) { 
        directories.pop(); 
        positions.pop(); 
        if (directories.isEmpty()) { 
         next = null; 
         return; 
        } 
        files = directories.peek(); 
       } 
       File nextFile = files[positions.peek()]; 
       if (nextFile.isDirectory()) { 
        int p = positions.pop() + 1; 
        positions.push(p); 
        if (recurse) { 
         directories.push(nextFile.listFiles(filter)); 
         positions.push(0); 
         advance(); 
        } else { 
         advance(); 
        } 
       } else { 
        next = nextFile.toURI().toString(); 
        count++; 
        if (count % 100 == 0) { 
         System.err.println(count + " " + next); 
        } 
        int p = positions.pop() + 1; 
        positions.push(p); 
       } 
      } 
     } 
    } 

Ich verstehe möchte, wie viele „offene Dateien“ dies erfordert. Unter welchen Umständen öffnet dieser Algorithmus eine Datei und wann wird sie wieder geschlossen?

Ich habe einige nette Code mit Hilfe von Java 7 oder Java 8, gesehen, aber ich bin auf Java 6.

+0

einfach Ihren Code mit mehr als 1.000.000 Dateien auf einem Dateisystem lief, und habe nicht das Problem, das Sie sehen. Ich verwende JDK 1.6.0_34 unter Windows. Vielleicht ist das Problem an anderer Stelle im Code? Können Sie den Code für den 'FilenameFilter' posten, den Sie verwenden? Das könnte das Problem sein. – msandiford

+0

Es kann sein, dass Ihr Dateisystem nicht so tief ist, sodass Ressourcen vom GC an das Betriebssystem zurückgegeben werden. Oder vielleicht hat Ihr Betriebssystem ein größeres Limit für geöffnete Dateien. –

+0

Ja, ich lag letzte Nacht wach und fragte mich, ob der FileNameFilter dafür verantwortlich war. Aber nein: Die Methode accept() gibt 'neue Datei (dir, name) .isDirectory() || zurück pattern.matcher (name) .matches(); ' –

Antwort

6

eingeschränkt Wenn Sie nextFile.listFiles() aufrufen, eine darunter liegende Dateideskriptor das Verzeichnis lesen geöffnet . Es gibt keine Möglichkeit, diesen Deskriptor explizit zu schließen, daher verlassen Sie sich auf die Garbage Collection. Wenn Ihr Code einen tiefen Baum abstammt, sammelt er im Wesentlichen einen Stapel von nextFile-Instanzen, die nicht gesammelt werden können.

Schritt 1: setze nextFile = null vor dem Aufruf von advance(). Dies gibt das Objekt für die Speicherbereinigung frei.

Schritt 2: Möglicherweise müssen Sie System.gc() nach dem Nullen von nextFile aufrufen, um eine schnelle Speicherbereinigung zu unterstützen. Leider gibt es keine Möglichkeit, GC zu erzwingen.

Schritt 3: Möglicherweise müssen Sie das Limit für offene Dateien auf Ihrem Betriebssystem erhöhen. Unter Linux kann dies mit ulimit (1) geschehen.

Wenn Sie nach Java 7 oder höher migrieren können, löst DirectoryStream Ihr Problem. Anstatt nextFile.listFiles() zu verwenden, verwenden Sie Files.newDirectoryStream (nextFile.toPath()), um einen DirectoryStream zu erhalten. Sie können dann über den Stream iterieren und dann() schließen, um die Betriebssystemressourcen freizugeben. Jeder zurückgegebene Pfad kann mit toFile() wieder in eine Datei konvertiert werden. Es könnte jedoch sein, dass Sie einfach den Pfad anstelle von Datei umstellen möchten.

+0

Der Op erwähnt, dass er auf Java 6 beschränkt ist. –

+0

Sie haben Recht, Pfad ist nur> = Java 7. Ich bearbeite meine Antwort mit einer Java 6 Alternative. –

1

Vielen Dank für die Hilfe und Beratung. Ich stellte fest, dass das Problem tatsächlich darin liegt, was mit den Dateien gemacht wird, nachdem sie vom Iterator zurückgegeben wurden: Der "Client" -Code öffnet die Dateien so, wie sie geliefert werden, und räumt nicht richtig auf. Es wird dadurch erschwert, dass die zurückkommenden Dateien parallel bearbeitet werden.

Ich habe auch die DireectoryIterator neu geschrieben, die ich niemandem teilen einhüllen interessiert ist:

private static class DirectoryIterator implements Iterator<String> { 

     private Stack<Iterator<File>> directories; 
     private FilenameFilter filter; 
     private boolean recurse; 
     private String next = null; 

     public DirectoryIterator(Stack<Iterator<File>> directories, boolean recurse, FilenameFilter filter) { 
      this.directories = directories; 
      this.recurse = recurse; 
      this.filter = filter; 
      advance(); 
     } 

     public boolean hasNext() { 
      return next != null; 
     } 

     public String next() { 
      String s = next; 
      advance(); 
      return s; 
     } 

     public void remove() { 
      throw new UnsupportedOperationException(); 
     } 

     private void advance() { 
      if (directories.isEmpty()) { 
       next = null; 
      } else { 
       Iterator<File> files = directories.peek(); 
       while (!files.hasNext()) { 
        directories.pop(); 
        if (directories.isEmpty()) { 
         next = null; 
         return; 
        } 
        files = directories.peek(); 
       } 
       File nextFile = files.next(); 
       if (nextFile.isDirectory()) { 
        if (recurse) { 
         directories.push(Arrays.asList(nextFile.listFiles(filter)).iterator()); 
        } 
        advance(); 
       } else { 
        next = nextFile.toURI().toString(); 
       } 
      } 
     } 
    } 
Verwandte Themen