Listen Sie alle Dateien aus einem Verzeichnis rekursiv mit Java auf

84

Ich habe diese Funktion, die den Namen aller Dateien in einem Verzeichnis rekursiv druckt. Das Problem ist, dass mein Code sehr langsam ist, da er bei jeder Iteration auf ein Remote-Netzwerkgerät zugreifen muss.

Mein Plan ist es, zuerst alle Dateien rekursiv aus dem Verzeichnis zu laden und danach alle Dateien mit dem regulären Ausdruck durchzugehen, um alle Dateien herauszufiltern, die ich nicht möchte. Hat jemand einen besseren Vorschlag?

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

Dies ist nur ein Test später. Ich werde den Code nicht so verwenden, sondern den Pfad und das Änderungsdatum jeder Datei hinzufügen, die einem erweiterten regulären Ausdruck zu einem Array entspricht.

Hultner
quelle
1
... was ist die Frage? Suchen Sie nur nach einer Bestätigung, dass dieser Code funktioniert?
Richard JP Le Guen
Nein, ich weiß, dass dieser Code funktioniert, aber er ist sehr langsam und es fühlt sich dumm an, auf das Dateisystem zuzugreifen und den Inhalt für jedes Unterverzeichnis abzurufen, anstatt alles auf einmal abzurufen.
Hultner
1
Mögliches Duplikat von rekursiv
aufgelisteten

Antworten:

132

Angenommen, dies ist der tatsächliche Produktionscode, den Sie schreiben, dann schlage ich vor, die Lösung für diese Art von Dingen zu verwenden, die bereits gelöst wurden - speziell Apache Commons IOFileUtils.listFiles() . Es behandelt verschachtelte Verzeichnisse, Filter (basierend auf Name, Änderungszeit usw.).

Zum Beispiel für Ihre Regex:

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

Dadurch wird rekursiv nach Dateien gesucht, die dem ^(.*?)regulären Ausdruck entsprechen, und die Ergebnisse werden als Sammlung zurückgegeben.

Es ist erwähnenswert, dass dies nicht schneller ist als das Rollen Ihres eigenen Codes. Es macht dasselbe - das Durchsuchen eines Dateisystems in Java ist nur langsam. Der Unterschied ist, dass die Apache Commons-Version keine Fehler enthält.

Skaffman
quelle
Ich habe dort gesucht und von dort aus habe ich commons.apache.org/io/api-release/index.html?org/apache/commons/… verwendet , um die gesamte Datei aus dem Verzeichnis und den Unterverzeichnissen abzurufen und dann die Dateien so zu durchsuchen Sie passen zu meiner Regex. Oder liege ich falsch?
Hultner
Ja, das Problem dauert über eine Stunde, um den Ordner zu scannen. Jedes Mal, wenn ich das Programm starte, um nach Updates zu suchen, ist dies äußerst ärgerlich. Wäre es schneller, wenn ich diesen Teil des Programms in C und den Rest in Java schreiben würde, und wenn ja, wäre es ein signifikanter Unterschied? Im Moment habe ich den Code in der if isdir-Zeile geändert und hinzugefügt, sodass das Verzeichnis auch mit einem regulären Ausdruck übereinstimmen muss, um in die Suche aufgenommen zu werden. Ich sehe, dass in Ihrem Beispiel DirectoryFileFilter.DIRECTORY steht. Ich denke, ich könnte dort einen Regex-Filter haben.
Hultner
1
Das Schreiben mit nativen Aufrufen würde es absolut schneller machen - Mit FindFirstFile / FineNextFile können Sie die Dateiattribute abfragen, ohne einen separaten Aufruf dafür durchführen zu müssen - dies kann massive Auswirkungen auf Netzwerke mit höherer Latenz haben. Javas Ansatz ist schrecklich ineffizient.
Kevin Day
5
@ hanzallah-afgan: Sowohl die Frage als auch die Antwort sind über 5 Jahre alt. In der Vergangenheit gab es zwei wichtige Java-Versionen, sodass Sie möglicherweise neuere Funktionen wie Java 7 NIO nicht untersuchen möchten.
Hultner
4
Verwenden Sie FileUtils nur, wenn Sie den Leistungseinbruch kennen und akzeptieren: github.com/brettryan/io-recurse-tests . Native Java8-Alternativen ermöglichen eine knappe und effizientere Notation, z. B.:Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList())
ccpizza
64

In Java 8 handelt es sich um einen 1-Liner-Via Files.find()mit einer beliebig großen Tiefe (z. B. 999) und BasicFileAttributesvonisRegularFile()

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

Um weitere Filter hinzuzufügen, verbessern Sie das Lambda, z. B. alle in den letzten 24 Stunden geänderten JPG-Dateien:

(p, bfa) -> bfa.isRegularFile()
  && p.getFileName().toString().matches(".*\\.jpg")
  && bfa.lastModifiedTime().toMillis() > System.currentMillis() - 86400000
Böhmisch
quelle
3
Ich schlage vor, immer die Files-Methoden zu verwenden, die Stream in Try-with-Resources-Blöcken zurückgeben. Andernfalls
bleibt
27

Dies ist eine sehr einfache rekursive Methode, um alle Dateien von einem bestimmten Stamm abzurufen.

Es verwendet die Java 7 NIO Path-Klasse.

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;
} 
Dan
quelle
18

Mit Java 7 wurde eine schnellere Möglichkeit zum Durchlaufen eines Verzeichnisbaums mit der Pathsund Files-Funktionalität eingeführt. Sie sind viel schneller als der "alte" FileWeg.

Dies wäre der Code zum Durchlaufen und Überprüfen der Pfadnamen mit einem regulären Ausdruck:

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;
        }
    });
}
jboi
quelle
5
Schöne Antwort :), es gibt auch eine implementierte Klasse namens "SimpleFileVisitor". Wenn Sie nicht alle implementierten Funktionen benötigen, können Sie einfach die benötigten Funktionen überschreiben.
GalDude33
13

Der schnelle Weg, um den Inhalt eines Verzeichnisses mit Java 7 NIO abzurufen:

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();
RealHowTo
quelle
3
Schön, bekommt aber nur Dateien für ein Verzeichnis. Wenn Sie alle Unterverzeichnisse sehen möchten, sehen Sie meine alternative Antwort.
Dan
3
Files.newDirectoryStreamkann eine IOException auslösen. Ich schlage vor, diese Zeile in eine Java7-Try-with-Anweisung zu verpacken, damit der Stream für Sie immer geschlossen bleibt (Ausnahme oder nicht, ohne dass ein a erforderlich ist finally). Siehe auch hier: stackoverflow.com/questions/17739362/…
Greg
12

Die Java-Oberfläche zum Lesen von Ordnerinhalten im Dateisystem ist nicht sehr leistungsfähig (wie Sie festgestellt haben). JDK 7 behebt dieses Problem mit einer völlig neuen Schnittstelle für diese Art von Dingen, die die Leistung auf nativer Ebene für diese Art von Vorgängen verbessern soll.

Das Hauptproblem besteht darin, dass Java für jede einzelne Datei einen nativen Systemaufruf ausführt. Auf einer Schnittstelle mit geringer Latenz ist dies keine große Sache - aber in einem Netzwerk mit selbst mäßiger Latenz summiert sich das wirklich. Wenn Sie Ihren Algorithmus oben profilieren, werden Sie feststellen, dass der Großteil der Zeit im lästigen Aufruf von isDirectory () verbracht wird. Dies liegt daran, dass Sie für jeden einzelnen Aufruf von isDirectory () einen Roundtrip durchführen. Die meisten modernen Betriebssysteme können diese Art von Informationen bereitstellen, wenn die Liste der Dateien / Ordner ursprünglich angefordert wurde (anstatt jeden einzelnen Dateipfad nach seinen Eigenschaften abzufragen).

Wenn Sie nicht auf JDK7 warten können, besteht eine Strategie zur Behebung dieser Latenz darin, Multithreading zu verwenden und einen ExecutorService mit einer maximalen Anzahl von Threads zu verwenden, um Ihre Rekursion durchzuführen. Es ist nicht großartig (Sie müssen sich mit dem Sperren Ihrer Ausgabedatenstrukturen befassen), aber es wird verdammt viel schneller sein als dieses einzelne Threaded.

In all Ihren Diskussionen über diese Art von Dingen empfehle ich Ihnen dringend, sich mit dem Besten zu vergleichen, das Sie mit nativem Code (oder sogar einem Befehlszeilenskript, das ungefähr dasselbe tut) tun können. Zu sagen, dass das Durchqueren einer Netzwerkstruktur eine Stunde dauert, bedeutet nicht wirklich viel. Wenn Sie uns sagen, dass Sie es in 7 Sekunden nativ machen können, aber in Java dauert es eine Stunde, wird die Aufmerksamkeit der Leute auf sich gezogen.

Kevin Day
quelle
3
Java 7 ist jetzt da, daher wäre ein Beispiel dafür in Java 7 hilfreich. Oder zumindest einen Link. Oder einen Klassennamen, nach dem bei Google gesucht werden soll. - das ist doch «stackoverflow» und doch nicht «theoretisches cs» ;-).
Martin
3
Mal sehen ... Mein ursprünglicher Beitrag war im März 2010 ... Es ist jetzt Januar 2012 ... Und ich habe gerade meine Gerätebestandshistorie überprüft, und ich sehe nicht, dass ich im März '10 eine Zeitmaschine hatte. Ich denke, ich bin wahrscheinlich berechtigt zu antworten, ohne ein explizites Beispiel zu geben ;-)
Kevin Day
6

Dies wird gut funktionieren ... und es ist rekursiv

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
        }
    }
}
Prathamesh Sawant
quelle
1
Gute Antwort, wenn Sie etwas wollen, das mit Java <7 funktioniert.
ssimm
3

Ich persönlich mag diese Version von FileUtils. Hier ist ein Beispiel, das alle MP3s oder Flacs in einem Verzeichnis oder einem seiner Unterverzeichnisse findet:

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

Dies wird gut funktionieren

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

Mams
quelle
Willkommen bei StackOverflow Mam's. Können Sie klarstellen, wie Ihre Antwort eine Verbesserung oder Alternative zu den vielen vorhandenen Antworten darstellt?
Lilienthal
1

Diese Funktion listet wahrscheinlich den gesamten Dateinamen und den Pfad aus dem Verzeichnis und den Unterverzeichnissen auf.

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

    }

}
Vishal Mokal
quelle
1
In diesem Beispiel wird nicht berücksichtigt, dass die Methode listFiles () null zurückgeben kann und wird. docs.oracle.com/javase/7/docs/api/java/io/File.html#listFiles ()
Matt Jones
0

Es fühlt sich dumm an, auf das Dateisystem zuzugreifen und den Inhalt für jedes Unterverzeichnis abzurufen, anstatt alles auf einmal abzurufen.

Dein Gefühl ist falsch. So funktionieren Dateisysteme. Es gibt keinen schnelleren Weg (außer wenn Sie dies wiederholt oder für verschiedene Muster tun müssen, können Sie alle Dateipfade im Speicher zwischenspeichern, aber dann müssen Sie sich mit der Ungültigmachung des Caches befassen, dh was passiert, wenn Dateien hinzugefügt / entfernt / umbenannt werden die App läuft).

Michael Borgwardt
quelle
Ich möchte alle Dateien eines bestimmten Typs mit einem bestimmten Namensformat in eine Bibliothek laden, die dem Benutzer angezeigt wird. Bei jedem Start der App soll die Bibliothek aktualisiert werden, aber die Aktualisierung der Bibliothek dauert ewig. Die einzige Lösung, die ich habe, besteht darin, das Update im Hintergrund auszuführen, aber es ist immer noch ärgerlich, dass es so lange dauert, bis alle neuen Dateien geladen sind. Es muss einen besseren Weg geben, dies zu tun. Oder zumindest eine bessere Möglichkeit, die Datenbank zu aktualisieren. Es fühlt sich dumm an, alle Dateien zu durchlaufen, die es bereits einmal durchlaufen hat. Gibt es eine Möglichkeit, Updates nur schnell zu finden?
Hultner
@Hultner: Java 7 wird eine Funktion enthalten, mit der Sie über Dateisystemaktualisierungen benachrichtigt werden können. Dies funktioniert jedoch nur, während die App ausgeführt wird. Wenn Sie also nicht möchten, dass ständig ein Hintergrunddienst ausgeführt wird, hilft dies nicht. Es kann spezielle Probleme mit Netzwerkfreigaben geben, wie Kevin beschreibt, aber solange Sie darauf angewiesen sind, den gesamten Verzeichnisbaum zu durchsuchen, gibt es wirklich keinen besseren Weg.
Michael Borgwardt
Vielleicht könnten Sie einige Indexdateien erstellen. Wenn es eine Möglichkeit gibt, die Verzeichnisgröße zu überprüfen, können Sie einfach nach neuen Dateien suchen, wenn sich die Größe ändert.
James P.
@ James: Es gibt keine Möglichkeit, die Verzeichnisgröße zu überprüfen. Die Größe eines Verzeichnisses wird ermittelt, indem die Größe für jede Datei ermittelt und in allen mir bekannten Dateisystemen addiert wird. Eigentlich die Frage "Wie groß ist dieses Verzeichnis?" macht überhaupt keinen Sinn, wenn man Hardlinks berücksichtigt.
Michael Borgwardt
Du hast recht. Ich habe immer noch das Gefühl, dass Caching und / oder Fingerabdrücke den Prozess beschleunigen könnten.
James P.
0

Nur damit Sie wissen, dass isDirectory () eine ziemlich langsame Methode ist. Ich finde es ziemlich langsam in meinem Dateibrowser. Ich werde in eine Bibliothek schauen, um sie durch nativen Code zu ersetzen.

Daniel Ryan
quelle
0

Der effizientere Weg, mit Millionen von Ordnern und Dateien umzugehen, besteht darin, die Verzeichnisliste mit dem DOS-Befehl in einer Datei zu erfassen und zu analysieren. Sobald Sie die Daten analysiert haben, können Sie Analysen durchführen und Statistiken berechnen.

Kiran
quelle
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);
   }
}
Prajakta
quelle
Bitte fügen Sie auch eine Erklärung hinzu.
d4Rk
0

In Guava müssen Sie nicht auf die Rückgabe einer Sammlung warten, sondern können die Dateien tatsächlich durchlaufen. Es ist leicht, sich eine IDoSomethingWithThisFileSchnittstelle in der Signatur der folgenden Funktion vorzustellen :

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

Mit TreeTraverser können Sie auch zwischen verschiedenen Durchlaufstilen wechseln .

Marcus Junius Brutus
quelle
0

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


    }
Niraj Sonawane
quelle
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());
    }
}
Sri
quelle
0

Ein weiterer optimierter Code

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());
    }
}
Sri
quelle
Können Sie bitte Ihre Antwort mit einer detaillierteren Erklärung erweitern? Dies ist sehr nützlich für das Verständnis. Danke dir!
Vezunchik
0

Ein weiteres Beispiel für das Auflisten von Dateien und Verzeichnissen mit 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");
        }
}
Uddhav Gautam
quelle