Elenca tutti i file da una directory in modo ricorsivo con Java

Ho questa funzione che stampa il nome di tutti i file in una directory in modo ricorsivo. Il problema è che il mio codice è molto lento perché deve accedere a un dispositivo di rete remoto ad ogni iterazione.

Il mio piano è quello di caricare prima tutti i file dalla directory in modo ricorsivo e poi passare attraverso tutti i file con la regex per filtrare tutti i file che non voglio. Qualcuno ha un suggerimento migliore?

public static printFnames(String sDir){  File[] faFiles = new File(sDir).listFiles();  for(File file: faFiles){ if(file.getName().matches("^(.*?)")){  System.out.println(file.getAbsolutePath()); }  if(file.isDirectory()){   printFnames(file.getAbsolutePath());  }  } } 

Questo è solo un test più tardi non userò il codice come questo, invece aggiungerò il percorso e la data di modifica di ogni file che corrisponde a un’espressione regolare di un array.

Supponendo che questo sia il codice di produzione effettivo che si scriverà, quindi suggerisco di utilizzare la soluzione per questo genere di cose che sono già state risolte – Apache Commons IO , in particolare FileUtils.listFiles() . Gestisce directory annidate, filtri (basati su nome, tempo di modifica, ecc.).

Ad esempio, per la tua espressione regolare:

 Collection files = FileUtils.listFiles( dir, new RegexFileFilter("^(.*?)"), DirectoryFileFilter.DIRECTORY ); 

Questo cercherà ricorsivamente i file che corrispondono alla regex ^(.*?) , Restituendo i risultati come una raccolta.

Vale la pena notare che questo non sarà più veloce di far ruotare il proprio codice, sta facendo la stessa cosa – la trawling di un filesystem in Java è solo lenta. La differenza è che la versione Apache Commons non contiene bug.

In Java 8, è un 1-liner tramite Files.find() con profondità arbitrariamente grande (ad es. 999 ) e BasicFileAttributes di isRegularFile()

 public static printFnames(String sDir) { Files.find(Paths.get(sDir), 999, (p, bfa) -> bfa.isRegularFile()).forEach(System.out::println); } 

Per aggiungere più filtri, potenzia il lambda, ad esempio tutti i file jpg modificati nelle ultime 24 ore:

 (p, bfa) -> bfa.isRegularFile() && p.getFileName().toString().matches(".*\\.jpg") && bfa.lastModifiedTime().toMillis() > System.currentMillis() - 86400000 

Questo è un metodo ricorsivo molto semplice per ottenere tutti i file da una determinata radice.

Utilizza la class Java NIO Path.

 private List getFileNames(List fileNames, Path dir) { try(DirectoryStream stream = Files.newDirectoryStream(dir)) { for (Path path : stream) { if(path.toFile().isDirectory()) { getFileNames(fileNames, path); } else { fileNames.add(path.toAbsolutePath().toString()); System.out.println(path.getFileName()); } } } catch(IOException e) { e.printStackTrace(); } return fileNames; } 

Con Java 7 è stato introdotto un modo più veloce di attraversare un albero di directory con la funzionalità Paths and Files . Sono molto più veloci del “vecchio” modo File .

Questo sarebbe il codice per attraversare e controllare i nomi dei percorsi con un’espressione regolare:

 public final void test() throws IOException, InterruptedException { final Path rootDir = Paths.get("path to your directory where the walk starts"); // Walk thru mainDir directory Files.walkFileTree(rootDir, new FileVisitor() { // First (minor) speed up. Compile regular expression pattern only one time. private Pattern pattern = Pattern.compile("^(.*?)"); @Override public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes atts) throws IOException { boolean matches = pattern.matcher(path.toString()).matches(); // TODO: Put here your business logic when matches equals true/false return (matches)? FileVisitResult.CONTINUE:FileVisitResult.SKIP_SUBTREE; } @Override public FileVisitResult visitFile(Path path, BasicFileAttributes mainAtts) throws IOException { boolean matches = pattern.matcher(path.toString()).matches(); // TODO: Put here your business logic when matches equals true/false return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path path, IOException exc) throws IOException { // TODO Auto-generated method stub return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOException { exc.printStackTrace(); // If the root directory has failed it makes no sense to continue return path.equals(rootDir)? FileVisitResult.TERMINATE:FileVisitResult.CONTINUE; } }); } 

L’interfaccia di Java per la lettura dei contenuti delle cartelle del filesystem non è molto performante (come hai scoperto). JDK 7 lo aggiusta con un’interfaccia completamente nuova per questo tipo di cose, che dovrebbe portare prestazioni di livello nativo a questo tipo di operazioni.

Il problema principale è che Java effettua una chiamata di sistema nativa per ogni singolo file. Su un’interfaccia a bassa latenza, questo non è un grosso problema, ma su una rete con una latenza anche moderata, si aggiunge davvero. Se profili il tuo algoritmo sopra, scoprirai che la maggior parte del tempo viene speso nella fastidiosa chiamata isDirectory (), perché stai incorrendo in un round trip per ogni singola chiamata a isDirectory (). La maggior parte dei sistemi operativi moderni è in grado di fornire questo tipo di informazioni quando l’elenco di file / cartelle è stato originariamente richiesto (invece di interrogare ogni singolo percorso file per le sue proprietà).

Se non puoi aspettare JDK7, una strategia per affrontare questa latenza è quella di passare al multithreading e utilizzare un ExecutorService con un numero massimo di thread per eseguire la tua ricorsione. Non è eccezionale (devi gestire il blocco delle strutture dei dati di output), ma sarà molto più veloce di fare questo singolo thread.

In tutte le tue discussioni su questo genere di cose, ti raccomando caldamente di confrontarti con il meglio che puoi fare usando il codice nativo (o anche uno script da riga di comando che fa grosso modo la stessa cosa). Dire che ci vuole un’ora per attraversare una struttura di rete in realtà non significa molto. Dicendoci che puoi farlo nativo in 7 secondi, ma ci vuole un’ora in Java attirerà l’attenzione della gente.

Il modo rapido per ottenere il contenuto di una directory utilizzando NIO Java 7:

 import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.FileSystems; import java.nio.file.Path; ... Path dir = FileSystems.getDefault().getPath( filePath ); DirectoryStream stream = Files.newDirectoryStream( dir ); for (Path path : stream) { System.out.println( path.getFileName() ); } stream.close(); 

questo funzionerà bene … ed è ricorsivo

 File root = new File("ROOT PATH"); for ( File file : root.listFiles()) { getFilesRecursive(file); } private static void getFilesRecursive(File pFile) { for(File files : pFile.listFiles()) { if(files.isDirectory()) { getFilesRecursive(files); } else { // do your thing // you can either save in HashMap and use it as // per your requirement } } } 

Personalmente mi piace questa versione di FileUtils. Ecco un esempio che trova tutti gli mp3 o flac in una directory o in una qualsiasi delle sue sottodirectory:

 String[] types = {"mp3", "flac"}; Collection files2 = FileUtils.listFiles(/path/to/your/dir, types , true); 

Questo funzionerà bene

 public void displayAll(File path){ if(path.isFile()){ System.out.println(path.getName()); }else{ System.out.println(path.getName()); File files[] = path.listFiles(); for(File dirOrFile: files){ displayAll(dirOrFile); } } } 

Questa funzione probabilmente elencerà tutto il nome del file e il suo percorso dalla sua directory e dalle sue sottodirectory.

 public void listFile(String pathname) { File f = new File(pathname); File[] listfiles = f.listFiles(); for (int i = 0; i < listfiles.length; i++) { if (listfiles[i].isDirectory()) { File[] internalFile = listfiles[i].listFiles(); for (int j = 0; j < internalFile.length; j++) { System.out.println(internalFile[j]); if (internalFile[j].isDirectory()) { String name = internalFile[j].getAbsolutePath(); listFile(name); } } } else { System.out.println(listfiles[i]); } } } 

sembra che sia stupido accedere al filesystem e ottenere il contenuto per ogni sottodirectory invece di ottenere tutto in una volta.

La tua sensazione è sbagliata. Ecco come funzionano i filesystem. Non esiste un modo più veloce (eccetto quando devi farlo ripetutamente o per diversi pattern, puoi memorizzare nella cache tutti i percorsi dei file in memoria, ma poi devi gestire l’invalidazione della cache, cioè cosa succede quando i file vengono aggiunti / rimossi / rinominati mentre l’app funziona).

Solo così sai che isDirectory () è un metodo abbastanza lento. Lo trovo piuttosto lento nel mio browser di file. Controllerò una libreria per sostituirla con codice nativo.

Il modo più efficiente che ho trovato nell’affrontare milioni di cartelle e file è quello di acquisire elenchi di directory tramite il comando DOS in alcuni file e analizzarli. Dopo aver analizzato i dati, è ansible eseguire analisi e statistiche di calcolo.

 import java.io.*; public class MultiFolderReading { public void checkNoOfFiles (String filename) throws IOException { File dir=new File(filename); File files[]=dir.listFiles();//files array stores the list of files for(int i=0;i 

In Guava non devi aspettare che venga restituito un insieme, ma può effettivamente scorrere i file. È facile immaginare un’interfaccia IDoSomethingWithThisFile nella firma della funzione seguente:

 public static void collectFilesInDir(File dir) { TreeTraverser traverser = Files.fileTreeTraverser(); FluentIterable filesInPostOrder = traverser.preOrderTraversal(dir); for (File f: filesInPostOrder) System.out.printf("File: %s\n", f.getPath()); } 

TreeTraverser ti permette anche tra vari stili di attraversamento.

Java 8

 public static void main(String[] args) throws IOException { Path start = Paths.get("C:\\data\\"); try (Stream stream = Files.walk(start, Integer.MAX_VALUE)) { List collect = stream .map(String::valueOf) .sorted() .collect(Collectors.toList()); collect.forEach(System.out::println); } }