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


86

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

Il mio piano è 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 in seguito Non userò il codice in questo modo, invece aggiungerò il percorso e la data di modifica di ogni file che corrisponde a un'espressione regolare avanzata a un array.


1
... quale è la domanda? Stai solo cercando la conferma che questo codice funzionerà?
Richard JP Le Guen

No, so che questo codice funziona ma è molto lento e sembra che sia stupido accedere al filesystem e ottenere i contenuti per ogni sottodirectory invece di ottenere tutto in una volta.
Hultner

Risposte:


134

Supponendo che questo sia il codice di produzione effettivo che scriverete, quindi suggerisco di utilizzare la soluzione a questo genere di cose che è già stata risolta - Apache Commons IO , in particolare FileUtils.listFiles(). Gestisce directory annidate, filtri (basati su nome, ora di modifica, ecc.).

Ad esempio, per la tua regex:

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

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

Vale la pena notare che questo non sarà più veloce del rollio del tuo codice, sta facendo la stessa cosa: la pesca a strascico di un filesystem in Java è solo lenta. La differenza è che la versione di Apache Commons non avrà bug al suo interno.


Ho guardato lì e da lì avrei usato commons.apache.org/io/api-release/index.html?org/apache/commons/… per ottenere tutto il file dalla directory e dalle sottodirectory e quindi cercare tra i file in modo che corrispondono alla mia regex. O mi sbaglio?
Hultner

Sì problema, ci vuole più di un'ora per scansionare la cartella e farlo ogni volta che avvio il programma per verificare la presenza di aggiornamenti è estremamente fastidioso. Sarebbe più veloce se scrivessi questa parte del programma in C e il resto in Java e in tal caso ci sarebbero differenze significative? Per ora ho cambiato il codice sulla riga if isdir e l'ho aggiunto in modo che anche la directory debba corrispondere a una regex per essere inclusa nella ricerca. Vedo che nel tuo esempio dice DirectoryFileFilter.DIRECTORY, immagino che potrei avere un filtro regex lì.
Hultner

1
scriverlo utilizzando chiamate native lo renderebbe assolutamente più veloce - FindFirstFile / FineNextFile ti consente di interrogare gli attributi del file senza dover effettuare una chiamata separata per esso - questo può avere enormi implicazioni per le reti a latenza più elevata. L'approccio di Java a questo è orribilmente inefficiente.
Kevin Day

5
@ hanzallah-afgan: Sia la domanda che la risposta hanno più di 5 anni. Ci sono state due importanti versioni di Java nel corso del tempo, quindi potresti non voler indagare sulle funzionalità più recenti come Java 7 NIO.
Hultner

4
Utilizza FileUtils solo se sei a conoscenza e accetti l'hit di prestazioni: github.com/brettryan/io-recurse-tests . Le alternative native a java8 consentono una notazione Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList())
concisa

66

In Java 8, è un via di 1 riga Files.find()con una profondità arbitrariamente grande (ad esempio 999) e BasicFileAttributesdiisRegularFile()

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

Per aggiungere più filtri, migliora 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

3
Suggerisco di usare sempre quei metodi Files che restituiscono Stream nei blocchi try-with-resources: altrimenti manterrai la risorsa aperta
riccardo.tasso

Le operazioni del terminale non si chiudono sullo stream stesso?
Dragas

@Dragas sì. Il mio consumatore è solo un semplice esempio; nella vita reale faresti qualcosa di più utile.
Bohemian

27

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

Utilizza la classe Java 7 NIO Path.

private List<String> getFileNames(List<String> fileNames, Path dir) {
    try(DirectoryStream<Path> 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;
} 

18

Con Java 7 è stato introdotto un modo più veloce per attraversare un albero di directory con le funzionalità Pathse Files. Sono molto più veloci del "vecchio" Filemodo.

Questo sarebbe il codice per camminare 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<Path>() {
        // 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;
        }
    });
}

5
Bella risposta :), c'è anche una classe implementata chiamata "SimpleFileVisitor", se non hai bisogno di tutte le funzioni implementate, puoi semplicemente sovrascrivere le funzioni necessarie.
GalDude33

13

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

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<Path> stream = Files.newDirectoryStream( dir );
for (Path path : stream) {
   System.out.println( path.getFileName() );
}
stream.close();

3
Bello ma ottiene solo i file per una directory. Se vuoi vedere tutte le sottodirectory vedi la mia risposta alternativa.
Dan

3
Files.newDirectoryStreampuò generare una IOException. Suggerisco di racchiudere quella riga in un'istruzione try-with-with Java7 in modo che il flusso sia sempre chiuso per te (eccezione o meno, senza la necessità di a finally). Vedi anche qui: stackoverflow.com/questions/17739362/…
Greg

12

L'interfaccia di Java per leggere il contenuto delle cartelle del filesystem non è molto performante (come hai scoperto). JDK 7 risolve questo problema con un'interfaccia completamente nuova per questo genere di cose, che dovrebbe portare prestazioni a 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 esegui il profilo del tuo algoritmo sopra, scoprirai che la maggior parte del tempo viene speso nella fastidiosa chiamata isDirectory (), perché stai incorrendo in un viaggio di andata e ritorno per ogni singola chiamata a isDirectory (). La maggior parte dei sistemi operativi moderni può fornire questo tipo di informazioni quando l'elenco di file / cartelle è stato originariamente richiesto (invece di interrogare ogni singolo percorso di file per le sue proprietà).

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

In tutte le tue discussioni su questo genere di cose, ti consiglio vivamente di confrontare con il meglio che potresti fare usando il codice nativo (o anche uno script della riga di comando che fa più o meno la stessa cosa). Dire che ci vuole un'ora per attraversare una struttura di rete non significa davvero molto. Dicendoci che puoi farlo in nativo in 7 secondi, ma ci vuole un'ora in Java attirerà l'attenzione della gente.


3
Java 7 è ora disponibile, quindi sarebbe utile un esempio su come farlo in Java 7. O almeno un collegamento. O un nome di classe da cercare su Google. - questo è «stackoverflow» e non «cs teorico» dopo tutto ;-).
Martin

3
beh, vediamo ... Il mio post originale era nel marzo 2010 ... Ora è gennaio 2012 ... E ho appena controllato la cronologia dell'inventario delle mie attrezzature, e non mi vedo avere una macchina del tempo nel marzo '10 quindi penso di essere probabilmente giustificato nel rispondere senza dare un esempio esplicito ;-)
Kevin Day


7

questo funzionerà benissimo ... 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
        }
    }
}

1
Buona risposta se vuoi qualcosa che funzioni con java <7.
ssimm

3

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

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

3

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


Benvenuto in StackOverflow Mam's, potresti chiarire in che modo la tua risposta è un miglioramento o un'alternativa alle molte risposte esistenti?
Lilienthal

1

Questa funzione probabilmente elencherà 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]);
        }

    }

}

1
Questo esempio non tiene conto del fatto che il metodo listFiles () può e restituirà null. docs.oracle.com/javase/7/docs/api/java/io/File.html#listFiles ()
Matt Jones

1

Java 8

public static void main(String[] args) throws IOException {

        Path start = Paths.get("C:\\data\\");
        try (Stream<Path> stream = Files.walk(start, Integer.MAX_VALUE)) {
            List<String> collect = stream
                .map(String::valueOf)
                .sorted()
                .collect(Collectors.toList());

            collect.forEach(System.out::println);
        }


    }

0

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

La tua sensazione è sbagliata. È così che funzionano i filesystem. Non esiste un modo più veloce (tranne quando devi farlo ripetutamente o per modelli diversi, puoi memorizzare nella cache tutti i percorsi dei file, ma poi devi affrontare l'invalidazione della cache cioè cosa succede quando i file vengono aggiunti / rimossi / rinominati mentre l'app viene eseguita).


Il fatto è che voglio caricare tutti i file di un certo tipo con un certo formato di nome in una libreria che viene presentata all'utente e ogni volta che l'app viene avviata la libreria dovrebbe essere aggiornata ma ci vuole un'eternità per aggiornare la libreria. L'unica soluzione che ho ottenuto è eseguire l'aggiornamento in background, ma è comunque fastidioso che ci voglia così tanto tempo prima che tutti i nuovi file vengano caricati. Deve esserci un modo migliore per farlo. O almeno un modo migliore per aggiornare il database. Sembra stupido passare attraverso tutti i file che ha già passato una volta. C'è un modo per trovare solo gli aggiornamenti velocemente.
Hultner

@Hultner: Java 7 includerà una funzione per ricevere notifiche sugli aggiornamenti del file system, ma funzionerà comunque solo mentre l'app è in esecuzione, quindi a meno che tu non voglia avere un servizio in background sempre in esecuzione, non sarebbe d'aiuto. Potrebbero esserci problemi speciali con le condivisioni di rete come descrive Kevin, ma fintanto che dipendi dalla scansione dell'intero albero delle directory, non c'è davvero modo migliore.
Michael Borgwardt

Forse potresti creare alcuni file di indice. Se c'è un modo per controllare la dimensione della directory, puoi semplicemente cercare nuovi file quando la dimensione cambia.
James P.

@ James: non c'è modo di controllare la dimensione della directory. La dimensione di una directory si ottiene ottenendo la dimensione di ogni file e sommandoli, in tutti i filesystem di cui sono a conoscenza. In realtà, la domanda "qual è la dimensione di questa directory?" non ha nemmeno necessariamente senso se si considerano gli hardlink.
Michael Borgwardt

Hai ragione. Sento ancora che alcune operazioni di memorizzazione nella cache e / o impronte digitali potrebbero accelerare il processo.
James P.

0

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


0

Il modo più efficiente che ho trovato nell'affrontare milioni di cartelle e file è acquisire l'elenco delle directory tramite il comando DOS in alcuni file e analizzarlo. Dopo aver analizzato i dati, è possibile eseguire analisi e calcolare statistiche.


0
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<files.length;i++)
    {
        if(files[i].isFile()) //check whether files[i] is file or directory
        {
            System.out.println("File::"+files[i].getName());
            System.out.println();

        }
        else if(files[i].isDirectory())
        {
            System.out.println("Directory::"+files[i].getName());
            System.out.println();
            checkNoOfFiles(files[i].getAbsolutePath());
        }
    }
}

public static void main(String[] args) throws IOException {

    MultiFolderReading mf=new MultiFolderReading();
    String str="E:\\file"; 
    mf.checkNoOfFiles(str);
   }
}

Per favore aggiungi anche qualche spiegazione.
d4Rk

0

In Guava non devi aspettare che ti venga restituita una raccolta, ma puoi effettivamente iterare sui file. È facile immaginare IDoSomethingWithThisFileun'interfaccia nella firma della funzione seguente:

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

TreeTraverser ti consente anche di scegliere tra vari stili di attraversamento.


0
public class GetFilesRecursive {
    public static List <String> getFilesRecursively(File dir){
        List <String> ls = new ArrayList<String>();
        for (File fObj : dir.listFiles()) {
            if(fObj.isDirectory()) {
                ls.add(String.valueOf(fObj));
                ls.addAll(getFilesRecursively(fObj));               
            } else {
                ls.add(String.valueOf(fObj));       
            }
        }

        return ls;
    }
    public static List <String> getListOfFiles(String fullPathDir) {
        List <String> ls = new ArrayList<String> ();
        File f = new File(fullPathDir);
        if (f.exists()) {
            if(f.isDirectory()) {
                ls.add(String.valueOf(f));
                ls.addAll(getFilesRecursively(f));
            }
        } else {
            ls.add(fullPathDir);
        }
        return ls;
    }

    public static void main(String[] args) {
        List <String> ls = getListOfFiles("/Users/srinivasab/Documents");
        for (String file:ls) {
            System.out.println(file);
        }
        System.out.println(ls.size());
    }
}

0

Un altro codice ottimizzato

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class GetFilesRecursive {
    public static List <String> getFilesRecursively(File dir){
        List <String> ls = new ArrayList<String>();
        if (dir.isDirectory())
            for (File fObj : dir.listFiles()) {
                if(fObj.isDirectory()) {
                    ls.add(String.valueOf(fObj));
                    ls.addAll(getFilesRecursively(fObj));               
                } else {
                    ls.add(String.valueOf(fObj));       
                }
            }
        else
            ls.add(String.valueOf(dir));

        return ls;
    }

    public static void main(String[] args) {
        List <String> ls = getFilesRecursively(new File("/Users/srinivasab/Documents"));
        for (String file:ls) {
            System.out.println(file);
        }
        System.out.println(ls.size());
    }
}

Per favore, puoi estendere la tua risposta con una spiegazione più dettagliata? Questo sarà molto utile per la comprensione. Grazie!
vezunchik

0

Un altro esempio di elenco di file e directory utilizzando Java 8 filter

public static void main(String[] args) {

System.out.println("Files!!");
        try {
            Files.walk(Paths.get("."))
                    .filter(Files::isRegularFile)
                    .filter(c ->
                            c.getFileName().toString().substring(c.getFileName().toString().length()-4).contains(".jpg")
                            ||
                            c.getFileName().toString().substring(c.getFileName().toString().length()-5).contains(".jpeg")
                    )
                    .forEach(System.out::println);

        } catch (IOException e) {
        System.out.println("No jpeg or jpg files");
        }

        System.out.println("\nDirectories!!\n");
        try {
            Files.walk(Paths.get("."))
                    .filter(Files::isDirectory)
                    .forEach(System.out::println);

        } catch (IOException e) {
            System.out.println("No Jpeg files");
        }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.