Lista recursiva de archivos en Java

258

¿Cómo enumero recursivamente todos los archivos en un directorio en Java? ¿El marco proporciona alguna utilidad?

Vi muchas implementaciones hacky. Pero ninguno del framework o nio

Quintin Par
fuente
2
Acabo de completar los resultados de las pruebas que proporcionan pruebas de rendimiento para muchas de las respuestas. Como era de esperar, todas las respuestas basadas en NIO funcionan mejor. La respuesta commons-io es claramente la de peor desempeño con más del doble de la duración de la carrera.
Brett Ryan
2
Java8: Files.walk?
Benj

Respuestas:

327

Java 8 proporciona una buena secuencia para procesar todos los archivos en un árbol.

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

Esto proporciona una forma natural de atravesar archivos. Dado que es una secuencia, puede realizar todas las operaciones de secuencia agradables en el resultado, como límite, agrupación, asignación, salida anticipada, etc.

ACTUALIZACIÓN : podría señalar que también hay Files.find, que toma un BiPredicate que podría ser más eficiente si necesita verificar los atributos del archivo.

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

Tenga en cuenta que si bien JavaDoc elude que este método podría ser más eficiente que Files.walk , es efectivamente idéntico, la diferencia en el rendimiento se puede observar si también está recuperando atributos de archivo dentro de su filtro. Al final, si necesita filtrar los atributos, use Files.find , de lo contrario use Files.walk , principalmente porque hay sobrecargas y es más conveniente.

PRUEBAS : Según lo solicitado, proporcioné una comparación de rendimiento de muchas de las respuestas. Echa un vistazo al proyecto Github que contiene resultados y un caso de prueba .

Brett Ryan
fuente
66
Uno de esos ejemplos que puede mostrar la magia de la programación funcional incluso para los principiantes.
Johnny
2
¿Cómo se compara el rendimiento de esto con los métodos anteriores a Java 8? Mi recorrido actual del directorio es demasiado lento y estoy buscando algo que lo acelere.
Sridhar Sarnobat
1
Estoy escribiendo algunas pruebas que contienen la mayoría de las variantes en las respuestas proporcionadas. Hasta ahora, parece que usar Files.walkcon un flujo paralelo es el mejor, seguido de cerca por lo Files.walkFileTreecual es solo un poco más lento. La respuesta aceptada usando commons-io es, con mucho, la más lenta, según mis pruebas, 4 veces más lenta.
Brett Ryan
1
@BrettRyan, probé tu solución pero recibo una excepción Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException. ¿Cómo podría corregirlo
Kachna
55
¿Cómo obtengo una lista real de archivos de esto?
thouliha
159

FileUtils tiene iterateFilesy listFilesmétodos. Pruébalos (de commons-io )

Editar: puede consultar aquí para obtener un punto de referencia de diferentes enfoques. Parece que el enfoque commons-io es lento, así que elija algunos de los más rápidos desde aquí (si es importante)

Bozho
fuente
40
FYI / TLDR: si solo desea enumerar todos los archivos de forma recursiva sin filtrado, haga FileUtils.listFiles(dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE), donde dirhay un objeto File que apunta al directorio base.
andronikus
2
Es posible que desee considerar el uso listFilesAndDirs(), ya listFiles()que no devuelve carpetas vacías.
schnatterer
1
@MikeFHay Mirando el código FileUtils, creo que debería serlo FileUtils.listFiles(dir, true, true). utilizando FileUtils.listFiles(dir, null, true)arrojará una excepción, mientras FileUtils.listFiles(dir, true, null)que enumerará todos los archivos sin buscar en subdirectorios.
ocramot
¿Qué tal una biblioteca nativa JDK? Puedo implementar esto fácilmente, pero simplemente sería C&P de otros lugares
Christian Bongiorno
1
Estoy poniendo algunas pruebas juntas, pero hasta ahora parece funcionar 4 veces más lento que el uso de alternativas JDK8 o JDK7. Los enlaces simbólicos también resultan problemáticos con este enfoque, especialmente cuando se vinculan a directorios más altos en el árbol, esto hace que el método nunca regrese, esto puede evitarse manejando el filtro, pero desafortunadamente los enlaces simbólicos en sí mismos no son visitados incluso un archivo.
Brett Ryan
138

// Listo para correr

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

}
apilador
fuente
9
Solo tenga en cuenta que para los enlaces simbólicos que apuntan a una ruta más alta en la jerarquía de la ruta, el método nunca terminará. Considere una ruta con un enlace simbólico que apunta a -> ..
Brett Ryan
2
Esto es esencialmente una mala implementación de Files.walkFileTree. Recomendaría que la gente mire FIles.walkFileTree en lugar de intentar rodarlo usted mismo ... Tiene un manejo para el problema exacto @BrettRyan lo señaló.
Tyler Nichols
Gracias por incluir import java.io.File ;. Muchos ejemplos se olvidan de incluir el espacio de nombres o incluso el tipo de datos, lo que hace que el ejemplo sea un punto de partida en un viaje de descubrimiento. Aquí este ejemplo está listo para ejecutarse. Gracias.
barrypicker
La ruta puede variar dependiendo de dónde esté el archivo Filewalker. Uso "/", "./"o "../"para el directorio raíz, el directorio de trabajo actual y el directorio principal, respectivamente
Moses Kirathe
67

Java 7 tendrá tiene Files.walkFileTree :

Si proporciona un punto de partida y un visitante de archivos, invocará varios métodos en el visitante de archivos mientras recorre el archivo en el árbol de archivos. Esperamos que las personas usen esto si están desarrollando una copia recursiva, un movimiento recursivo, una eliminación recursiva o una operación recursiva que establece permisos o realiza otra operación en cada uno de los archivos.

Ahora hay un tutorial Oracle sobre esta pregunta .

bostezo
fuente
Y nunca notifica el final del paseo.
Farid
25

No se necesitan bibliotecas externas.
Devuelve una Colección para que pueda hacer lo que quiera después de la llamada.

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
fuente
simple y limpio
Leo
17

Yo iría con algo como:

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

System.out.println está ahí para indicar que debe hacer algo con el archivo. no es necesario diferenciar entre archivos y directorios, ya que un archivo normal simplemente tendrá cero hijos.

Stefan Schmidt
fuente
66
De la documentación de listFiles(): "Si este nombre de ruta abstracto no denota un directorio, entonces este método regresa null".
hfs
Variante mejorada Public static Collection <File> listFileTree (File dir) {if (null == dir ||! Dir.isDirectory ()) {return Collections.emptyList (); } final Set <File> fileTree = new HashSet <File> (); for (Entrada de archivo: dir.listFiles ()) {if (entry.isFile ()) {fileTree.add (entrada); } else {fileTree.addAll (listFileTree (entrada)); }} return fileTree; }
Ben
Para mí, esta es la respuesta más concisa que es recursiva.
WillieT
14

Prefiero usar una cola sobre la recursividad para este tipo de conversión simple:

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
fuente
Pero su algoritmo no puede imprimir con salida con sangría. Dirs y archivos están desordenados. ¿Alguna solución?
Wei
12

solo escríbelo tú mismo usando una recursión simple:

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
fuente
1
¡Por favor! deje que la persona que llama inicialice la lista de archivos para que no tenga que verificar su nulidad cada vez. Si desea crear un segundo método (público) que cree la lista, llame a este método interno y devuelva la lista completa.
helios
1
lo que sea. un cheque nulo no es muy costoso, aparte de la conveniencia + preferencia personal, creo que entenderá el punto.
pstanton
¿Puedes explicar un poco más verbosamente?
uday
8

Creo que esto debería hacer el trabajo:

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

De esta manera tienes archivos y directorios. Ahora use la recursión y haga lo mismo para los directorios (la Fileclase tiene isDirectory()método).

Michał Niklas
fuente
8

Con Java 7 puede usar la siguiente clase:

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
fuente
7

En Java 8, ahora podemos usar la utilidad Archivos para recorrer un árbol de archivos. Muy simple.

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

Este código está listo para ejecutarse

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 '
fuente
4

Además del recorrido recursivo, también se puede utilizar un enfoque basado en el visitante.

El siguiente código utiliza el enfoque basado en el visitante para el recorrido. Se espera que la entrada al programa sea el directorio raíz para atravesar.

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
fuente
3

Puede usar el siguiente código para obtener una lista de archivos de carpetas o directorios específicos de forma recursiva.

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
fuente
2

La respuesta aceptada es excelente, sin embargo, se descompone cuando quieres hacer IO dentro de la lambda.

Esto es lo que puede hacer si su acción declara IOExceptions.

Puede tratar la secuencia filtrada como un Iterable, y luego realizar su acción en un ciclo regular para cada ciclo. De esta manera, no tiene que manejar excepciones dentro de 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);
    }
}

Encontré ese truco aquí: https://stackoverflow.com/a/32668807/1207791

cfstras
fuente
1

BFS no recursivo con una sola lista (un ejemplo particular es buscar archivos * .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);
        }
    }
bobah
fuente
1

Mi versión (por supuesto, podría haber utilizado el paseo incorporado en 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;
    }
usuario1189332
fuente
1

Aquí una solución simple pero que funciona perfectamente usando 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
fuente
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
fuente
1

Se me ocurrió esto para imprimir todos los archivos / nombres de archivo de forma recursiva.

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
fuente
0

El ejemplo genera archivos * .csv en subdirectorios de búsqueda recursiva de directorios usando Files.find () de 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);
        }

    }

Publicando este ejemplo, ya que tuve problemas para entender cómo pasar el parámetro del nombre de archivo en el ejemplo # 1 dado por Bryan, usando foreach en Stream-result -

Espero que esto ayude.

Ralf R.
fuente
0

Kotlin tiene FileTreeWalkpara este propósito. Por ejemplo:

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

Producirá una lista de texto de todos los archivos que no son de directorio bajo una raíz determinada, un archivo por línea con la ruta relativa a la raíz y la longitud.

Clyde
fuente
0

Otra forma de hacerlo, incluso si alguien ya proporciona caminar Java 8.

Este te proporcionará todos los archivos de forma recursiva

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

Basado en la respuesta del apilador. Aquí hay una solución que funciona en JSP sin bibliotecas externas para que pueda colocarla en casi cualquier lugar de su servidor:

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

Entonces solo haces algo como:

    <ul>
        <% for (String file : files) { %>
            <% if (file.matches(".+\\.(apk|ipa|mobileprovision)")) { %>
                <li><%=file.replace(prefixPath, "")%></li>
            <% } %>
        <% } %>
    </ul>
Nux
fuente
1
Si bien probablemente funcione, la pregunta es sobre la exploración de archivos, no sobre la representación de archivos examinados. Exponga mejor su algoritmo como tal, no es una práctica recomendada incorporar lógica de negocios dentro de un JSP.
Samuel Kerrien
Eso depende de lo que estés haciendo. En una aplicación de tamaño empresarial, tiene toda la razón. Si solo necesita esto como un complemento a una lista simple e independiente, entonces esto está perfectamente bien.
Nux