2015-11-10 12 views
7

Ho implementato (in Java) un Iterator abbastanza semplice per restituire i nomi dei file in una struttura di directory ricorsiva e dopo circa 2300 file non è riuscito "Troppi file aperti nel sistema" (l'errore era in realtà nel tentativo di caricare una classe, ma presumo che l'elenco di directory fosse il colpevole).Errore "Troppi file aperti nel sistema" mentre si elenca una struttura di directory ricorsiva

La struttura dati gestita dall'iteratore è una pila contenente i contenuti delle directory aperte a ciascun livello.

La logica attuale è piuttosto semplice:

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

vorrei capire quanti "open files" questo richiede. In quali circostanze questo algoritmo "apre" un file e quando si chiude nuovamente?

Ho visto alcuni di codice pulito utilizzando Java o Java 7 8, ma sono costretti a Java 6.

+0

appena eseguito il codice su un filesystem con oltre 1.000.000 di file, e non capisco il problema che si sta vedendo. Sto usando JDK 1.6.0_34 su Windows. Forse il problema è altrove nel codice? Puoi pubblicare il codice per 'FilenameFilter' che stai utilizzando? Questo potrebbe essere il problema. – msandiford

+0

Potrebbe essere che il tuo filesystem non sia così profondo, quindi le risorse vengono restituite al sistema operativo dal GC. O forse il tuo sistema operativo ha un limite maggiore per i file aperti. –

+0

Sì, sono rimasto sveglio la notte scorsa chiedendomi se il FileNameFilter fosse la colpa. Ma no: il metodo accept() restituisce il nuovo file (dir, name) .isDirectory() || pattern.matcher (name) .matches(); ' –

risposta

6

Quando si chiama nextFile.listFiles(), un descrittore di file sottostante viene aperto per leggere la directory . Non c'è modo di chiudere esplicitamente questo descrittore, quindi ti stai affidando alla garbage collection. Mentre il tuo codice scende da un albero profondo, sta essenzialmente raccogliendo una pila di istanze nextFile che non possono essere raccolte garbaged.

Passaggio 1: impostare nextFile = null prima di chiamare advance(). Questo rilascia l'oggetto per la garbage collection.

Passaggio 2: potrebbe essere necessario chiamare System.gc() dopo aver annullato nextFile per incoraggiare la raccolta rapida dei dati inutili. Sfortunatamente, non c'è modo di forzare GC.

Passaggio 3: potrebbe essere necessario aumentare il limite di file aperti sul sistema operativo. Su Linux questo può essere fatto con ulimit (1).

Se è possibile migrare a Java 7 o versioni successive, DirectoryStream risolverà il problema. Invece di usare nextFile.listFiles(), utilizzare Files.newDirectoryStream (nextFile.toPath()) per ottenere un DirectoryStream. È quindi possibile eseguire iterazioni sullo stream e quindi chiuderlo() per rilasciare le risorse del sistema operativo. Ogni percorso restituito può essere riconvertito in un file con toFile(). Ad ogni modo, ti piacerebbe che ti rifatti di usare solo Path invece di File.

+0

L'opting è limitato a Java 6. –

+0

Hai ragione, Path is only> = Java 7. Modificherò la mia risposta con un'alternativa Java 6. –

1

Grazie a tutti per l'aiuto e i consigli. Ho stabilito che il problema è in realtà in ciò che viene fatto con i file dopo che sono stati restituiti dall'iteratore: il codice "client" sta aprendo i file mentre vengono consegnati e non riordina correttamente. È complicato dal fatto che i file in arrivo vengono effettivamente elaborati in parallelo.

Ho anche riscritto il DireectoryIterator, che condivido in caso qualcuno fosse interessato:

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(); 
       } 
      } 
     } 
    } 
Problemi correlati