Elenco ricorsivo dei file in Java


258

Come posso elencare ricorsivamente tutti i file in una directory in Java? Il framework fornisce qualche utilità?

Ho visto molte implementazioni confuse. Ma nessuno dal framework o nio


2
Ho appena completato i risultati dei test che forniscono test delle prestazioni per molte delle risposte. Non sorprende che tutte le risposte basate su NIO abbiano le migliori prestazioni. La risposta commons-io è chiaramente la peggiore in termini di prestazioni con oltre il doppio della durata della corsa.
Brett Ryan,

2
Java8: Files.walk?
Benj,

Risposte:


327

Java 8 fornisce un bel flusso per elaborare tutti i file in un albero.

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

Ciò fornisce un modo naturale per attraversare i file. Dal momento che è un flusso puoi fare tutte le belle operazioni del flusso sul risultato come limite, raggruppamento, mappatura, uscita anticipata ecc.

AGGIORNAMENTO : Potrei sottolineare che c'è anche Files.find che accetta un BiPredicate che potrebbe essere più efficiente se è necessario controllare gli attributi del file.

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

Si noti che mentre JavaDoc elude che questo metodo potrebbe essere più efficiente di Files.walk è effettivamente identico, la differenza nelle prestazioni può essere osservata se si stanno anche recuperando gli attributi di file all'interno del filtro. Alla fine, se hai bisogno di filtrare gli attributi usa Files.find , altrimenti usa Files.walk , principalmente perché ci sono sovraccarichi ed è più conveniente.

PROVE : Come richiesto, ho fornito un confronto delle prestazioni di molte delle risposte. Dai un'occhiata al progetto Github che contiene risultati e un caso di test .


6
Uno di quegli esempi che possono mostrare la magia della programmazione funzionale anche per i principianti.
Johnny,

2
Come si confronta la prestazione di questo con i metodi pre-java 8? Il mio attuale attraversamento delle directory è troppo lento e sto cercando qualcosa che lo acceleri.
Sridhar Sarnobat,

1
Sto scrivendo alcuni test contenenti la maggior parte delle varianti nelle risposte fornite. Finora, sembra che l'uso Files.walkcon un flusso parallelo sia il migliore, seguito da vicino Files.walkFileTreeche è solo leggermente più lento. La risposta accettata utilizzando commons-io è di gran lunga la più lenta dai miei test per essere 4 volte più lenta.
Brett Ryan,

1
@BrettRyan, ho provato la tua soluzione ma ho un'eccezione Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException. Come potrei correggerlo
Kachna,

5
Come posso ottenere un elenco effettivo di file da questo?
thouliha,

159

FileUtils ha iterateFilese listFilesmetodi. Provali. (da commons-io )

Modifica: è possibile verificare qui un punto di riferimento di diversi approcci. Sembra che l'approccio commons-io sia lento, quindi scegli alcuni tra quelli più veloci da qui (se è importante)


40
FYI / TLDR: se si desidera elencare tutti i file in modo ricorsivo senza filtri, fare FileUtils.listFiles(dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE), dove si dirtrova un oggetto File che punta alla directory di base.
andronikus,

2
Potresti voler considerare l'utilizzo listFilesAndDirs(), in quanto listFiles()non restituisce cartelle vuote.
schnatterer,

1
@MikeFHay Guardando il codice FileUtils, penso che dovrebbe essere FileUtils.listFiles(dir, true, true). l'utilizzo FileUtils.listFiles(dir, null, true)genererà un'eccezione, mentre FileUtils.listFiles(dir, true, null)elencherà tutti i file senza cercare nelle sottodirectory.
Ocramot,

Che ne dici di una libreria nativa JDK? Posso implementarlo facilmente, ma sarei semplicemente C&P da altri posti
Christian Bongiorno,

1
Sto mettendo insieme alcuni test, ma finora questo sembra funzionare 4 volte più lentamente di quello dell'utilizzo delle alternative JDK8 o JDK7. Anche i link simbolici si rivelano problematici con questo approccio, specialmente laddove si collegano a directory più alte nella struttura ad albero, ciò fa sì che il metodo non ritorni mai, questo può essere evitato gestendo il filtro, ma sfortunatamente i link simbolici stessi non vengono visitati anche se un file.
Brett Ryan,

138

// Pronto a correre

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:\\" );
    }

}

9
Basta fare attenzione che per i collegamenti simbolici che puntano a un percorso più in alto nella gerarchia del percorso, il metodo non finirà mai. Considera un percorso con un collegamento simbolico a cui punta -> ..
Brett Ryan,

2
Questa è essenzialmente una cattiva implementazione di Files.walkFileTree. Consiglierei che la gente guardi FIles.walkFileTree invece di provare a lanciarlo da solo ... Ha gestito l'esatto problema che @BrettRyan ha indicato.
Tyler Nichols,

Grazie per aver incluso l'importazione java.io.File ;. Quindi molti esempi dimenticano di includere le cose dello spazio dei nomi o persino quelle dei tipi di dati che rendono l'esempio un punto di partenza per un viaggio di scoperta. Qui questo esempio è pronto per essere eseguito. Grazie.
barrypicker,

Il percorso può variare in base alla posizione del file Filewalker. Utilizzare "/", "./"o "../"per la directory principale, la directory di lavoro corrente e la directory principale, rispettivamente
Moses Kirathe il

67

Java 7 avrà ha Files.walkFileTree :

Se si fornisce un punto di partenza e un visitatore di file, invocherà vari metodi sul visitatore di file mentre percorre il file nella struttura dei file. Ci aspettiamo che le persone lo utilizzino se stanno sviluppando una copia ricorsiva, una mossa ricorsiva, un'eliminazione ricorsiva o un'operazione ricorsiva che imposta le autorizzazioni o esegue un'altra operazione su ciascuno dei file.

Ora c'è un intero tutorial Oracle su questa domanda .


E non notifica mai la fine della camminata.
Farid,

25

Non sono necessarie librerie esterne.
Restituisce una raccolta in modo che tu possa fare quello che vuoi dopo la chiamata.

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

semplice e pulito
Leone

17

Vorrei andare con qualcosa di simile:

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

System.out.println è proprio lì per indicare di fare qualcosa con il file. non è necessario distinguere tra file e directory, poiché un file normale avrà semplicemente zero figli.


6
Dalla documentazione di listFiles(): "Se questo percorso astratto non indica una directory, questo metodo restituisce null."
hfs,

Variante migliorata Raccolta statica pubblica <File> listFileTree (Dir file) {if (null == dir ||! Dir.isDirectory ()) {return Collections.emptyList (); } final Set <File> fileTree = new HashSet <File> (); per (Voce file: dir.listFiles ()) {if (entry.isFile ()) {fileTree.add (entry); } else {fileTree.addAll (listFileTree (entry)); }} return fileTree; }
Ben

Per me questa è la risposta più concisa che è ricorsiva.
WillieT,

14

Preferisco usare una coda rispetto alla ricorsione per questo tipo di semplice traversione:

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

Ma il tuo algoritmo non può stampare con output indentato. Dir e file sono incasinati. Qualche soluzione?
Wei,

12

scrivilo tu stesso usando una semplice ricorsione:

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

1
Per favore! lasciare che il chiamante inizializzi l'elenco dei file in modo che non debba controllare la sua nullità ogni volta. Se si desidera creare un secondo metodo (pubblico) che crea l'elenco, chiama questo metodo interno e restituisce l'elenco completo.
helios,

1
qualunque cosa. un controllo nullo non è molto costoso, convenienza + preferenza personale a parte penso che otterrà il punto.
Pstanton,

Puoi spiegarci un po 'più verbalmente?
ud

8

Penso che questo dovrebbe fare il lavoro:

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

In questo modo hai file e directory. Ora usa la ricorsione e fai lo stesso per dirs (la Fileclasse ha il isDirectory()metodo).


8

Con Java 7 è possibile utilizzare la seguente classe:

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

7

In Java 8, ora possiamo usare l'utilità File per percorrere un albero di file. Molto semplice.

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

7

Questo codice è pronto per essere eseguito

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

4

Oltre all'attraversamento ricorsivo, si può usare anche un approccio basato sul Visitatore.

Sotto il codice viene utilizzato l'approccio basato sul visitatore per l'attraversamento. Si prevede che l'input per il programma sia la directory principale da attraversare.

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

3

È possibile utilizzare il codice seguente per ottenere ricorsivamente un elenco di file di cartelle o directory specifiche.

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

2

La risposta accettata è ottima, tuttavia si interrompe quando si desidera eseguire IO all'interno della lambda.

Ecco cosa puoi fare se la tua azione dichiara IOExceptions.

Puoi considerare il flusso filtrato come un Iterable, quindi eseguire la tua azione in un normale ciclo per ogni. In questo modo, non è necessario gestire le eccezioni all'interno di una lambda.

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

Ho trovato questo trucco qui: https://stackoverflow.com/a/32668807/1207791


1

BFS non ricorsivo con un unico elenco (un esempio particolare è la ricerca di file * .eml):

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

1

La mia versione (ovviamente avrei potuto usare la passeggiata integrata in Java 8 ;-)):

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

1

Ecco una soluzione semplice ma perfettamente funzionante che utilizza 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());
        }
    }
}

1
    private void fillFilesRecursively(File file, List<File> resultFiles) {
        if (file.isFile()) {
            resultFiles.add(file);
        } else {
            for (File child : file.listFiles()) {
                fillFilesRecursively(child, resultFiles);
            }
        }
    }

1

Ho pensato a questo per stampare ricorsivamente tutti i file / nomi di file.

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

0

Esempio di output * .csv file nella directory ricorsiva ricerca nelle sottodirectory usando Files.find () da 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);
        }

    }

Pubblicando questo esempio, poiché ho avuto difficoltà a capire come passare il parametro del nome file nell'esempio n. 1 fornito da Bryan, usando foreach su Stream-result -

Spero che questo ti aiuti.


0

Kotlin ha FileTreeWalka questo scopo. Per esempio:

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

Produrrà un elenco di testo di tutti i file non di directory in una determinata radice, un file per riga con il percorso relativo alla radice e alla lunghezza.


0

Un altro modo in cui puoi fare anche se qualcuno già fornisce Java 8 walk.

Questo ti fornirà tutti i file in modo ricorsivo

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

-1

Basato sulla risposta dell'impilatore. Ecco una soluzione che funziona in JSP senza librerie esterne in modo da poterla collocare praticamente ovunque sul tuo server:

<!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() + "/";
%>

Quindi fai semplicemente qualcosa del tipo:

    <ul>
        <% for (String file : files) { %>
            <% if (file.matches(".+\\.(apk|ipa|mobileprovision)")) { %>
                <li><%=file.replace(prefixPath, "")%></li>
            <% } %>
        <% } %>
    </ul>

1
Anche se probabilmente funziona, la domanda riguarda la ricerca dei file, non il rendering dei file sfogliati. Meglio esporre l'algoritmo in quanto tale, non è una pratica consigliata incorporare la logica aziendale all'interno di un JSP.
Samuel Kerrien,

Dipende da cosa stai facendo. In un'applicazione di dimensioni aziendali hai perfettamente ragione. Se hai solo bisogno di questo come un drop-in per un elenco semplice e autonomo, allora va benissimo.
Nux,
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.