2016-01-17 17 views
15

Sembra che nio .list restituisca un flusso che, una volta consumato, trattiene un descrittore di file per file iterato, finché .close non viene richiamato nell'intero flusso. Ciò significa che le directory di dati con un massimo di 1.000 file possono facilmente sfiorare i valori comuni di ulimit. L'effetto complessivo di questo accumulo di descrittori di file, esacerba ulteriormente quando si ha a che fare con gli attraversamenti nidificati.Iterazione di file in scala/java in O (1) descrittori di file aperti

Quale potrebbe essere un modo alternativo per scorrere i file di directory di grandi dimensioni, oltre a passare alle chiamate di generazione al comando dell'elenco di file del sistema operativo? Sarebbe bello se iterando i file di una directory di grandi dimensioni, un descrittore di file venisse mantenuto solo per il file attualmente iterato, come implicito dalla corretta semantica dello stream.

Edit:

list restituisce un flusso di java di java.nio.file.Path Quale chiamata API sarebbe stato utilizzato per la chiusura di ogni voce del flusso una volta che è stato elaborato, piuttosto che solo quando l'intero flusso viene chiuso, per l'iterazione più snella? In scala, questo può essere facilmente manipolato utilizzando il wrapper API da file migliori, che porta da here.

+0

"tiene a uno descrittore di file per file iterati, fino .close si chiama su l'intero flusso "Come sei arrivato a quella conclusione? – Tunaki

+1

Sono arrivato a questa conclusione contando i descrittori di file tramite JMX (Scala 2.11 su Oracle java 8, su Ubuntu), prima e dopo aver iterato il risultato di '.list', con e senza chiamare' close' dopo l'iterazione. – matanster

+1

Aveva lo stesso problema con l'RDD personalizzato in Spark. Aggiunto un elenco di connessioni aperte e un metodo close() per chiudere tutte le connessioni aperte alla fine. Forse potresti modificare il codice iteratore per chiudere un file già in streaming. –

risposta

2

Ho riscontrato lo stesso problema (su Windows Server 2012 R2) quando non chiudevo il flusso. Tutti i file su cui è stata eseguita l'iterazione erano aperti in modalità lettura finché la JVM non veniva chiusa. Tuttavia, non si è verificato su Mac OS X e poiché lo streaming dipende dalle implementazioni dipendenti dal sistema operativo di FileSystemProvider e DirectoryStream, presumo che il problema possa dipendere anche dal sistema operativo.

Contrariamente al commento @Ian McLaird, è menzionato nella documentazione Files.list() che

Se è necessaria una tempestiva disposizione delle risorse di sistema di file, il costrutto try-con-le risorse dovrebbero essere utilizzati per garantire che il il metodo di chiusura di stream viene richiamato al termine delle operazioni di streaming.

Il flusso restituito è un DirectoryStream, il cui Javadoc dice:

Un DirectoryStream si apre al momento della creazione ed è chiusa invocando il metodo close. La chiusura di un flusso di directory rilascia tutte le risorse associate al flusso. La mancata chiusura del flusso potrebbe causare una perdita di risorse.

La mia soluzione era quella di seguire il consiglio e utilizzare il costrutto try-with-resources

try (Stream<Path> fileListing = Files.list(directoryPath)) { 
    // use the fileListing stream 
} 

Quando ho chiuso il flusso corretto (usato il sopra try-with-resources costrutto), gli handle di file sono stati immediatamente rilasciati.

Se non si cura di ottenere i file come un ruscello o sei OK con il caricamento l'intero elenco dei file in memoria e convertirlo in un flusso di te stesso, è possibile utilizzare l'API IO:

File directory = new File("/path/to/dir"); 
File[] files = directory.listFiles(); 
if (files != null) { // 'files' can be null if 'directory' "does not denote a directory, or if an I/O error occurs." 
    // use the 'files' array or convert to a stream: 
    Stream<File> fileStream = Arrays.stream(files); 
} 

Non ho riscontrato alcun problema di blocco dei file con questo. Tuttavia, si noti che entrambe le soluzioni si basano su codice nativo, dipendente dal sistema operativo, quindi consiglio di eseguire test in tutti gli ambienti che si utilizzeranno.

+0

Perché non è bastato chiudere lo stream nel tuo caso? sembri riecheggiare la mia esperienza in cui un gestore di file è stato preso (e accumulato) per file iterato, che in seguito non sono riuscito a riprodurre. – matanster

+0

Anche se la mia domanda era una sorta di non-domanda, perché, non potevo riprodurre che i gestori di file sono stati presi semplicemente ripetendo la directory, sto assegnando la taglia qui come questa risposta sembra gettare più luce sulla questione generale, forse utile anche per altri casi/ricerche. Inoltre sono molto grato per la risposta panoramica e orientata alla ricerca. – matanster

+0

@matanster: grazie per l'apprezzamento! Non sono sicuro che ci siamo capiti correttamente - inizialmente I ** non ha ** chiuso lo stream e si è imbattuto nello stesso problema di te. Quando ho usato il costrutto 'try-with-resources' ** ha aiutato ** e gli handle del file sono stati immediatamente rilasciati (ma solo dopo aver chiuso il flusso). Ho modificato la risposta per enfatizzarla. Stavo sviluppando su Mac OS X in cui questo problema non si verificava, ma quando ho distribuito a Win Server 2012 R2, è successo. –

4

Se succede, perché non usare la vecchia scuola java.io.File?

File folder = new File(pathToFolder); 
String[] files = folder.list(); 

testati con lsof e sembra che nessun dei file elencati è aperto. È possibile convertire la matrice in un elenco o flusso in seguito. A meno che la directory non sia troppo grande o remota, cercherò di incolpare gli oggetti Path e di garbage-collect o in qualche modo di distruggerli.

1

È possibile utilizzare la libreria Apache fileutils, che utilizzano le vecchie java.io.File.listFiles funzionate internaly:

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