Listen Sie Dateien rekursiv in Java auf

258

Wie liste ich rekursiv alle Dateien in einem Verzeichnis in Java auf? Bietet das Framework einen Nutzen?

Ich habe viele hackige Implementierungen gesehen. Aber keine aus dem Framework oder nio

Quintin Par
quelle
2
Ich habe gerade Testergebnisse abgeschlossen , die Leistungstests für viele der Antworten bereitstellen. Es überrascht nicht, dass alle NIO-basierten Antworten die beste Leistung erbringen. Die Commons-Io-Antwort ist eindeutig die schlechteste Leistung mit mehr als der doppelten Lauflänge.
Brett Ryan
2
Java8: Files.walk?
Benj

Antworten:

327

Java 8 bietet einen schönen Stream, um alle Dateien in einem Baum zu verarbeiten.

Files.walk(Paths.get(path))
        .filter(Files::isRegularFile)
        .forEach(System.out::println);

Dies bietet eine natürliche Möglichkeit, Dateien zu durchlaufen. Da es sich um einen Stream handelt, können Sie alle netten Stream-Operationen für das Ergebnis ausführen, z. B. Limit, Gruppierung, Zuordnung, vorzeitiges Beenden usw.

UPDATE : Ich möchte darauf hinweisen, dass es auch Files.find gibt, das ein BiPredicate verwendet , das effizienter sein könnte, wenn Sie Dateiattribute überprüfen müssen.

Files.find(Paths.get(path),
           Integer.MAX_VALUE,
           (filePath, fileAttr) -> fileAttr.isRegularFile())
        .forEach(System.out::println);

Beachten Sie, dass JavaDoc zwar nicht der Meinung ist, dass diese Methode effizienter als Files.walk sein könnte, aber effektiv identisch ist. Der Leistungsunterschied kann jedoch beobachtet werden, wenn Sie auch Dateiattribute in Ihrem Filter abrufen . Wenn Sie nach Attributen filtern müssen , verwenden Sie am Ende Files.find , andernfalls Files.walk , hauptsächlich, weil es Überladungen gibt und dies bequemer ist.

TESTS : Wie gewünscht habe ich einen Leistungsvergleich vieler Antworten bereitgestellt. Schauen Sie sich das Github-Projekt an, das Ergebnisse und einen Testfall enthält .

Brett Ryan
quelle
6
Eines dieser Beispiele, das die Magie der funktionalen Programmierung auch für Anfänger zeigen kann.
Johnny
2
Wie ist die Leistung im Vergleich zu Methoden vor Java 8? Mein aktueller Verzeichnisdurchlauf ist zu langsam und ich suche nach etwas, das ihn beschleunigt.
Sridhar Sarnobat
1
Ich schreibe einige Tests auf, die die meisten Varianten in den Antworten enthalten. Bisher scheint die Verwendung Files.walkmit einem parallelen Stream die beste zu sein, dicht gefolgt von Files.walkFileTreeder nur geringfügig langsameren. Die akzeptierte Antwort mit commons-io ist bei meinen Tests mit Abstand die langsamste und viermal langsamer.
Brett Ryan
1
@BrettRyan, ich habe deine Lösung ausprobiert, aber ich bekomme eine Ausnahme Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException. Wie könnte ich das korrigieren
Kachna
5
Wie erhalte ich daraus eine aktuelle Liste der Dateien?
Thouliha
159

FileUtils haben iterateFilesund listFilesMethoden. Probieren Sie es aus. (von commons-io )

Bearbeiten: Hier können Sie nach einem Benchmark für verschiedene Ansätze suchen. Es scheint, dass der Commons-Io-Ansatz langsam ist. Wählen Sie daher einige der schnelleren von hier aus (falls es darauf ankommt).

Bozho
quelle
40
FYI / TLDR: Wenn Sie nur alle Dateien ohne Filterung rekursiv auflisten möchten, tun Sie dies FileUtils.listFiles(dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE), wobei dirsich ein Dateiobjekt befindet, das auf das Basisverzeichnis verweist.
Andronikus
2
Möglicherweise möchten Sie die Verwendung in Betracht ziehen listFilesAndDirs(), da listFiles()keine leeren Ordner zurückgegeben werden.
Schnatterer
1
@ MikeFHay Wenn ich mir den FileUtils-Code anschaue, denke ich, dass das so sein könnte FileUtils.listFiles(dir, true, true). using FileUtils.listFiles(dir, null, true)löst eine Ausnahme aus, während FileUtils.listFiles(dir, true, null)alle Dateien aufgelistet werden, ohne in Unterverzeichnisse zu schauen.
Ocramot
Wie wäre es mit einer nativen JDK-Bibliothek? Ich kann dies einfach umsetzen, aber ich wäre einfach C & P von anderen Orten
Christian Bongiorno
1
Ich stelle einige Tests zusammen, aber bisher scheint dies viermal langsamer zu sein als bei Verwendung von JDK8- oder JDK7-Alternativen. Symlinks erweisen sich bei diesem Ansatz ebenfalls als problematisch, insbesondere wenn sie mit Verzeichnissen höher im Baum verknüpft sind. Dies führt dazu, dass die Methode niemals zurückkehrt. Dies kann durch die Behandlung des Filters vermieden werden, aber leider werden die Symlinks selbst nicht einmal als besucht eine Datei.
Brett Ryan
138

// Bereit zu rennen

import java.io.File;

public class Filewalker {

    public void walk( String path ) {

        File root = new File( path );
        File[] list = root.listFiles();

        if (list == null) return;

        for ( File f : list ) {
            if ( f.isDirectory() ) {
                walk( f.getAbsolutePath() );
                System.out.println( "Dir:" + f.getAbsoluteFile() );
            }
            else {
                System.out.println( "File:" + f.getAbsoluteFile() );
            }
        }
    }

    public static void main(String[] args) {
        Filewalker fw = new Filewalker();
        fw.walk("c:\\" );
    }

}
Stapler
quelle
9
Beachten Sie jedoch, dass bei symbolischen Links, die auf einen Pfad in der Pfadhierarchie verweisen, die Methode niemals endet. Stellen Sie sich einen Pfad mit einem Symlink vor, der auf zeigt -> ..
Brett Ryan
2
Dies ist im Wesentlichen eine schlechte Implementierung von Files.walkFileTree. Ich würde empfehlen, dass die Leute sich FIles.walkFileTree ansehen, anstatt zu versuchen, es selbst zu rollen ... Es hat eine Behandlung für das genaue Problem, auf das @BrettRyan hingewiesen hat.
Tyler Nichols
Vielen Dank für den Import java.io.File;. So viele Beispiele vergessen, das Namespace-Zeug oder sogar das Datentyp-Zeug einzuschließen, was das Beispiel zu einem Ausgangspunkt für eine Entdeckungsreise macht. Hier ist dieses Beispiel betriebsbereit. Vielen Dank.
Barrypicker
Der Pfad kann je nach Speicherort der Filewalker-Datei variieren. Verwendung "/", "./"oder "../"für Stammverzeichnis, aktuelles Arbeitsverzeichnis und übergeordnetes Verzeichnis bzw.
Moses Kirathe
67

Java 7 wird hat Files.walkFileTree :

Wenn Sie einen Startpunkt und einen Dateibesucher angeben, werden beim Durchlaufen der Datei im Dateibaum verschiedene Methoden für den Dateibesucher aufgerufen. Wir erwarten, dass Benutzer dies verwenden, wenn sie eine rekursive Kopie, eine rekursive Verschiebung, eine rekursive Löschung oder eine rekursive Operation entwickeln, die Berechtigungen festlegt oder eine andere Operation für jede der Dateien ausführt.

Zu dieser Frage gibt es jetzt ein komplettes Oracle-Tutorial .

gähnen
quelle
Und es benachrichtigt nie das Ende des Spaziergangs.
Farid
25

Keine externen Bibliotheken erforderlich.
Gibt eine Sammlung zurück, damit Sie nach dem Anruf damit arbeiten können, was Sie wollen.

public static Collection<File> listFileTree(File dir) {
    Set<File> fileTree = new HashSet<File>();
    if(dir==null||dir.listFiles()==null){
        return fileTree;
    }
    for (File entry : dir.listFiles()) {
        if (entry.isFile()) fileTree.add(entry);
        else fileTree.addAll(listFileTree(entry));
    }
    return fileTree;
}
Petrucio
quelle
einfach und sauber
Leo
17

Ich würde mit so etwas gehen wie:

public void list(File file) {
    System.out.println(file.getName());
    File[] children = file.listFiles();
    for (File child : children) {
        list(child);
    }
}

Die Datei System.out.println dient nur dazu, anzuzeigen, dass etwas mit der Datei geschehen soll. Es ist nicht erforderlich, zwischen Dateien und Verzeichnissen zu unterscheiden, da eine normale Datei einfach keine untergeordneten Dateien hat.

Stefan Schmidt
quelle
6
Aus der Dokumentation von listFiles(): "Wenn dieser abstrakte Pfadname kein Verzeichnis bezeichnet, gibt diese Methode zurück null."
HFS
Verbesserte Variante public static Collection <Datei> listFileTree (Dateiverzeichnis) {if (null == dir ||! Dir.isDirectory ()) {return Collections.emptyList (); } final Set <Datei> fileTree = new HashSet <Datei> (); für (Dateieintrag: dir.listFiles ()) {if (entry.isFile ()) {fileTree.add (Eintrag); } else {fileTree.addAll (listFileTree (Eintrag)); }} return fileTree; }
Ben
Für mich ist dies die prägnanteste Antwort, die rekursiv ist.
WillieT
14

Ich bevorzuge die Verwendung einer Warteschlange gegenüber der Rekursion für diese Art der einfachen Durchquerung:

List<File> allFiles = new ArrayList<File>();
Queue<File> dirs = new LinkedList<File>();
dirs.add(new File("/start/dir/"));
while (!dirs.isEmpty()) {
  for (File f : dirs.poll().listFiles()) {
    if (f.isDirectory()) {
      dirs.add(f);
    } else if (f.isFile()) {
      allFiles.add(f);
    }
  }
}
Benroth
quelle
Ihr Algorithmus kann jedoch nicht mit eingerückter Ausgabe drucken. Dirs und Dateien sind durcheinander. Irgendeine Lösung?
Wei
12

Schreiben Sie es einfach selbst mit einer einfachen Rekursion:

public List<File> addFiles(List<File> files, File dir)
{
    if (files == null)
        files = new LinkedList<File>();

    if (!dir.isDirectory())
    {
        files.add(dir);
        return files;
    }

    for (File file : dir.listFiles())
        addFiles(files, file);
    return files;
}
pstanton
quelle
1
Bitte! Lassen Sie den Aufrufer die Dateiliste initialisieren, damit er nicht jedes Mal die Nichtigkeit überprüfen muss. Wenn Sie eine zweite (öffentliche) Methode erstellen möchten, die die Liste erstellt, rufen Sie diese interne Methode auf und geben Sie die vollständige Liste zurück.
Helios
1
was auch immer. Ein Null-Check ist nicht sehr teuer, Bequemlichkeit + persönliche Vorlieben beiseite, ich denke, er wird den Punkt bekommen.
Pstanton
Können Sie das etwas ausführlicher erklären?
uday
8

Ich denke, das sollte die Arbeit machen:

File dir = new File(dirname);
String[] files = dir.list();

Auf diese Weise haben Sie Dateien und Verzeichnisse. Verwenden Sie jetzt die Rekursion und machen Sie dasselbe für dirs ( FileKlasse hat isDirectory()Methode).

Michał Niklas
quelle
8

Mit Java 7 können Sie die folgende Klasse verwenden:

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

public class MyFileIterator extends SimpleFileVisitor<Path>
{
    public MyFileIterator(String path) throws Exception
    {
        Files.walkFileTree(Paths.get(path), this);
    }

    @Override
    public FileVisitResult visitFile(Path file,
            BasicFileAttributes attributes) throws IOException
    {
        System.out.println("File: " + file);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult preVisitDirectory(Path dir,
            BasicFileAttributes attributes) throws IOException
    {
        System.out.println("Dir: " + dir);
        return FileVisitResult.CONTINUE;
    }
}
Chao
quelle
7

In Java 8 können wir jetzt das Dienstprogramm "Dateien" verwenden, um einen Dateibaum zu durchlaufen. Sehr einfach.

Files.walk(root.toPath())
      .filter(path -> !Files.isDirectory(path))
      .forEach(path -> System.out.println(path));
Roy Kachouh
quelle
7

Dieser Code kann ausgeführt werden

public static void main(String... args) {
    File[] files = new File("D:/").listFiles();
    if (files != null) 
       getFiles(files);
}

public static void getFiles(File[] files) {
    for (File file : files) {
        if (file.isDirectory()) {
            getFiles(file.listFiles());
        } else {
            System.out.println("File: " + file);
        }
    }
}
Ebraheem Alrabee '
quelle
4

Neben der rekursiven Durchquerung kann auch ein Besucherbasierter Ansatz verwendet werden.

Der folgende Code verwendet einen auf Besuchern basierenden Ansatz für die Durchquerung. Es wird erwartet, dass die Eingabe in das Programm das zu durchlaufende Stammverzeichnis ist.

public interface Visitor {
    void visit(DirElement d);
    void visit(FileElement f);
}

public abstract class Element {
    protected File rootPath;
    abstract void accept(Visitor v);

    @Override
    public String toString() {
        return rootPath.getAbsolutePath();
    }
}

public class FileElement extends Element {
    FileElement(final String path) {
        rootPath = new File(path);
    }

    @Override
    void accept(final Visitor v) {
        v.visit(this);
    }
}

public class DirElement extends Element implements Iterable<Element> {
    private final List<Element> elemList;
    DirElement(final String path) {
        elemList = new ArrayList<Element>();
        rootPath = new File(path);
        for (File f : rootPath.listFiles()) {
            if (f.isDirectory()) {
                elemList.add(new DirElement(f.getAbsolutePath()));
            } else if (f.isFile()) {
                elemList.add(new FileElement(f.getAbsolutePath()));
            }
        }
    }

    @Override
    void accept(final Visitor v) {
        v.visit(this);
    }

    public Iterator<Element> iterator() {
        return elemList.iterator();
    }
}

public class ElementWalker {
    private final String rootDir;
    ElementWalker(final String dir) {
        rootDir = dir;
    }

    private void traverse() {
        Element d = new DirElement(rootDir);
        d.accept(new Walker());
    }

    public static void main(final String[] args) {
        ElementWalker t = new ElementWalker("C:\\temp");
        t.traverse();
    }

    private class Walker implements Visitor {
        public void visit(final DirElement d) {
            System.out.println(d);
            for(Element e:d) {
                e.accept(this);
            }
        }

        public void visit(final FileElement f) {
            System.out.println(f);
        }
    }
}
Sateesh
quelle
3

Sie können den folgenden Code verwenden, um eine Liste der Dateien eines bestimmten Ordners oder Verzeichnisses rekursiv abzurufen.

public static void main(String args[]) {

        recusiveList("D:");

    }

    public static void recursiveList(String path) {

        File f = new File(path);
        File[] fl = f.listFiles();
        for (int i = 0; i < fl.length; i++) {
            if (fl[i].isDirectory() && !fl[i].isHidden()) {
                System.out.println(fl[i].getAbsolutePath());
                recusiveList(fl[i].getAbsolutePath());
            } else {
                System.out.println(fl[i].getName());
            }
        }
    }
Rakesh Chaudhari
quelle
2

Die akzeptierte Antwort ist großartig, bricht jedoch zusammen, wenn Sie E / A innerhalb des Lambda ausführen möchten.

Folgendes können Sie tun, wenn Ihre Aktion IOExceptions deklariert.

Sie können den gefilterten Stream als einen behandeln Iterableund dann Ihre Aktion in einer regulären for-each-Schleife ausführen. Auf diese Weise müssen Sie keine Ausnahmen innerhalb eines Lambda behandeln.

try (Stream<Path> pathStream = Files.walk(Paths.get(path))
        .filter(Files::isRegularFile)) {

    for (Path file : (Iterable<Path>) pathStream::iterator) {
        // something that throws IOException
        Files.copy(file, System.out);
    }
}

Den Trick hier gefunden: https://stackoverflow.com/a/32668807/1207791

cfstras
quelle
1

Nicht rekursives BFS mit einer einzelnen Liste (besonderes Beispiel ist die Suche nach * .eml-Dateien):

    final FileFilter filter = new FileFilter() {
        @Override
        public boolean accept(File file) {
            return file.isDirectory() || file.getName().endsWith(".eml");
        }
    };

    // BFS recursive search
    List<File> queue = new LinkedList<File>();
    queue.addAll(Arrays.asList(dir.listFiles(filter)));

    for (ListIterator<File> itr = queue.listIterator(); itr.hasNext();) {
        File file = itr.next();
        if (file.isDirectory()) {
            itr.remove();
            for (File f: file.listFiles(filter)) itr.add(f);
        }
    }
Bobah
quelle
1

Meine Version (natürlich hätte ich den eingebauten Walk in Java 8 verwenden können ;-)):

public static List<File> findFilesIn(File rootDir, Predicate<File> predicate) {
        ArrayList<File> collected = new ArrayList<>();
        walk(rootDir, predicate, collected);
        return collected;
    }

    private static void walk(File dir, Predicate<File> filterFunction, List<File> collected) {
        Stream.of(listOnlyWhenDirectory(dir))
                .forEach(file -> walk(file, filterFunction, addAndReturn(collected, file, filterFunction)));
    }

    private static File[] listOnlyWhenDirectory(File dir) {
        return dir.isDirectory() ? dir.listFiles() : new File[]{};
    }

    private static List<File> addAndReturn(List<File> files, File toAdd, Predicate<File> filterFunction) {
        if (filterFunction.test(toAdd)) {
            files.add(toAdd);
        }
        return files;
    }
user1189332
quelle
1

Hier eine einfache, aber perfekt funktionierende Lösung mit recursion:

public static List<Path> listFiles(String rootDirectory)
{
    List<Path> files = new ArrayList<>();
    listFiles(rootDirectory, files);

    return files;
}

private static void listFiles(String path, List<Path> collectedFiles)
{
    File root = new File(path);
    File[] files = root.listFiles();

    if (files == null)
    {
        return;
    }

    for (File file : files)
    {
        if (file.isDirectory())
        {
            listFiles(file.getAbsolutePath(), collectedFiles);
        } else
        {
            collectedFiles.add(file.toPath());
        }
    }
}
BullyWiiPlaza
quelle
1
    private void fillFilesRecursively(File file, List<File> resultFiles) {
        if (file.isFile()) {
            resultFiles.add(file);
        } else {
            for (File child : file.listFiles()) {
                fillFilesRecursively(child, resultFiles);
            }
        }
    }
legendmohe
quelle
1

Ich habe mir das ausgedacht, um alle Dateien / Dateinamen rekursiv zu drucken.

private static void printAllFiles(String filePath,File folder) {
    if(filePath==null) {
        return;
    }
    File[] files = folder.listFiles();
    for(File element : files) {
        if(element.isDirectory()) {
            printAllFiles(filePath,element);
        } else {
            System.out.println(" FileName "+ element.getName());
        }
    }
}
Kanaparthikiran
quelle
0

Beispielausgaben * .csv-Dateien im Verzeichnis rekursive Suche Unterverzeichnisse mit Files.find () aus java.nio:

String path = "C:/Daten/ibiss/ferret/";
    logger.debug("Path:" + path);
    try (Stream<Path> fileList = Files.find(Paths.get(path), Integer.MAX_VALUE,
            (filePath, fileAttr) -> fileAttr.isRegularFile() && filePath.toString().endsWith("csv"))) {
        List<String> someThingNew = fileList.sorted().map(String::valueOf).collect(Collectors.toList());
        for (String t : someThingNew) {
            t.toString();
            logger.debug("Filename:" + t);
        }

    }

Ich habe dieses Beispiel gepostet, da ich Probleme hatte zu verstehen, wie der Dateinamenparameter in dem von Bryan angegebenen Beispiel Nr. 1 mit foreach on Stream-result übergeben wird.

Hoffe das hilft.

Ralf R.
quelle
0

Kotlin hat FileTreeWalkzu diesem Zweck. Beispielsweise:

dataDir.walkTopDown().filter { !it.isDirectory }.joinToString("\n") {
   "${it.toRelativeString(dataDir)}: ${it.length()}"
}

Erstellt eine Textliste aller Nicht-Verzeichnisdateien unter einem bestimmten Stammverzeichnis, eine Datei pro Zeile mit dem Pfad relativ zum Stammverzeichnis und zur Länge.

Clyde
quelle
0

Eine andere Möglichkeit ist auch dann, wenn jemand bereits Java 8 Walk bereitstellt.

Dieser liefert Ihnen alle Dateien rekursiv

  private Stream<File> files(File file) {
    return file.isDirectory()
            ? Arrays.stream(file.listFiles()).flatMap(this::files)
            : Stream.of(file);
}
Michael
quelle
-1

Basierend auf der Staplerantwort. Hier ist eine Lösung, die in JSP ohne externe Bibliotheken funktioniert, sodass Sie sie fast überall auf Ihrem Server ablegen können:

<!DOCTYPE html>
<%@ page session="false" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page contentType="text/html; charset=UTF-8" %>

<%!
    public List<String> files = new ArrayList<String>();
    /**
        Fills files array with all sub-files.
    */
    public void walk( File root ) {
        File[] list = root.listFiles();

        if (list == null) return;

        for ( File f : list ) {
            if ( f.isDirectory() ) {
                walk( f );
            }
            else {
                files.add(f.getAbsolutePath());
            }
        }
    }
%>
<%
    files.clear();
    File jsp = new File(request.getRealPath(request.getServletPath()));
    File dir = jsp.getParentFile();
    walk(dir);
    String prefixPath = dir.getAbsolutePath() + "/";
%>

Dann machst du einfach so etwas wie:

    <ul>
        <% for (String file : files) { %>
            <% if (file.matches(".+\\.(apk|ipa|mobileprovision)")) { %>
                <li><%=file.replace(prefixPath, "")%></li>
            <% } %>
        <% } %>
    </ul>
Nux
quelle
1
Während es wahrscheinlich funktioniert, geht es um das Durchsuchen von Dateien und nicht um das Rendern von durchsuchten Dateien. Stellen Sie Ihren Algorithmus besser als solchen dar. Es wird nicht empfohlen, Geschäftslogik in eine JSP einzubetten.
Samuel Kerrien
Das hängt davon ab, was Sie tun. In einer Anwendung in Unternehmensgröße haben Sie absolut Recht. Wenn Sie dies nur als Drop-In für eine einfache, eigenständige Auflistung benötigen, ist dies vollkommen in Ordnung.
Nux