Enumere todos los archivos de un directorio de forma recursiva con Java

85

Tengo esta función que imprime el nombre de todos los archivos en un directorio de forma recursiva. El problema es que mi código es muy lento porque tiene que acceder a un dispositivo de red remoto con cada iteración.

Mi plan es cargar primero todos los archivos del directorio de forma recursiva y luego revisar todos los archivos con la expresión regular para filtrar todos los archivos que no quiero. ¿Alguien tiene una sugerencia mejor?

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

Esto es solo una prueba más adelante. No voy a usar el código como este, sino que voy a agregar la ruta y la fecha de modificación de cada archivo que coincida con una expresión regular avanzada en una matriz.

Hultner
fuente
1
... ¿cuál es la pregunta? ¿Está buscando la validación de que este código funcionará?
Richard JP Le Guen
No, sé que este código funciona, pero es muy lento y se siente como una estupidez acceder al sistema de archivos y obtener el contenido de cada subdirectorio en lugar de obtener todo a la vez.
Hultner
1
posible duplicado de archivos de lista recursiva en Java
Prahalad Gaggar

Respuestas:

134

Suponiendo que este es el código de producción real que va a escribir, sugiero usar la solución para este tipo de cosas que ya se han resuelto: Apache Commons IO , específicamente FileUtils.listFiles(). Maneja directorios anidados, filtros (basados ​​en nombre, fecha de modificación, etc.).

Por ejemplo, para su expresión regular:

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

Esto buscará de forma recursiva archivos que coincidan con la ^(.*?)expresión regular y devolverá los resultados como una colección.

Vale la pena señalar que esto no será más rápido que ejecutar su propio código, está haciendo lo mismo: rastrear un sistema de archivos en Java es simplemente lento. La diferencia es que la versión de Apache Commons no tendrá errores.

skaffman
fuente
Miré allí y, a partir de ahí, usaría commons.apache.org/io/api-release/index.html?org/apache/commons/… para obtener todo el archivo del directorio y subdirectorios y luego buscar en los archivos para que coinciden con mi expresión regular. ¿O me equivoco?
Hultner
Sí, problema, se tarda más de una hora en escanear la carpeta y hacerlo cada vez que inicio el programa para buscar actualizaciones es extremadamente molesto. ¿Sería más rápido si escribiera esta parte del programa en C y el resto en Java y, de ser así, habría alguna diferencia significativa? Por ahora cambié el código en la línea if isdir y lo agregué para que el directorio también tenga que coincidir con una expresión regular para ser incluido en la búsqueda. Veo que en su ejemplo dice DirectoryFileFilter.DIRECTORY, supongo que podría tener un filtro de expresiones regulares allí.
Hultner
1
escribirlo utilizando llamadas nativas lo haría absolutamente más rápido: FindFirstFile / FineNextFile le permite consultar los atributos del archivo sin tener que realizar una llamada por separado; esto puede tener implicaciones masivas para redes de mayor latencia. El enfoque de Java para esto es terriblemente ineficiente.
Kevin Day
5
@hanzallah-afgan: Tanto la pregunta como la respuesta tienen más de 5 años. Ha habido dos versiones principales de Java durante el tiempo pasado, por lo que es posible que desee investigar características más nuevas como Java 7 NIO.
Hultner
4
Solo use FileUtils si conoce y acepta el resultado de rendimiento: github.com/brettryan/io-recurse-tests . Las alternativas nativas de java8 permiten una notación concisa y más eficiente, por ejemplo:Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList())
ccpizza
64

En Java 8, es una vía de 1 línea Files.find()con una profundidad arbitrariamente grande (por ejemplo 999) y BasicFileAttributesdeisRegularFile()

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

Para agregar más filtrado, mejore la lambda, por ejemplo, todos los archivos jpg modificados en las últimas 24 horas:

(p, bfa) -> bfa.isRegularFile()
  && p.getFileName().toString().matches(".*\\.jpg")
  && bfa.lastModifiedTime().toMillis() > System.currentMillis() - 86400000
Bohemio
fuente
3
Sugiero usar siempre los métodos de Archivos que devuelven Stream en bloques de prueba con recursos: de lo contrario, mantendrá el recurso abierto
riccardo.tasso
¿No se cierran las operaciones de la terminal en la transmisión?
Dragas
@Dragas sí. Mi consumidor es solo un simple ejemplo; en la vida real harías algo más útil.
Bohemio
27

Este es un método recursivo muy simple para obtener todos los archivos de una raíz determinada.

Utiliza la clase 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;
} 
Dan
fuente
18

Con Java 7, se introdujo una forma más rápida de recorrer un árbol de directorios con la funcionalidad Pathsy Files. Son mucho más rápidos que la forma "antigua" File.

Este sería el código para recorrer y verificar los nombres de las rutas con una expresión regular:

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
fuente
5
Buena respuesta :), también hay una clase implementada llamada "SimpleFileVisitor", si no necesita todas las funciones implementadas, puede simplemente Anular las funciones necesarias.
GalDude33
13

La forma rápida de obtener el contenido de un directorio usando 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();
RealHowTo
fuente
3
Agradable, pero solo obtiene archivos para un directorio. Si desea ver todos los subdirectorios, consulte mi respuesta alternativa.
Dan
3
Files.newDirectoryStreampuede lanzar una IOException. Sugiero envolver esa línea en una declaración de prueba de Java7 para que la transmisión siempre esté cerrada para usted (con excepción o no, sin la necesidad de a finally). Véase también aquí: stackoverflow.com/questions/17739362/…
Greg
12

La interfaz de Java para leer el contenido de las carpetas del sistema de archivos no es muy eficaz (como ha descubierto). JDK 7 corrige esto con una interfaz completamente nueva para este tipo de cosas, que debería brindar un rendimiento de nivel nativo a este tipo de operaciones.

El problema principal es que Java realiza una llamada al sistema nativo para cada archivo. En una interfaz de baja latencia, esto no es un gran problema, pero en una red con latencia incluso moderada, realmente se suma. Si perfila su algoritmo arriba, encontrará que la mayor parte del tiempo se gasta en la molesta llamada isDirectory (), eso es porque está incurriendo en un viaje de ida y vuelta por cada llamada a isDirectory (). La mayoría de los sistemas operativos modernos pueden proporcionar este tipo de información cuando se solicitó originalmente la lista de archivos / carpetas (en lugar de consultar cada ruta de archivo individual por sus propiedades).

Si no puede esperar a JDK7, una estrategia para abordar esta latencia es utilizar varios subprocesos y utilizar un ExecutorService con un número máximo de subprocesos para realizar su recursividad. No es genial (tienes que lidiar con el bloqueo de tus estructuras de datos de salida), pero será muchísimo más rápido que hacer este único subproceso.

En todas sus discusiones sobre este tipo de cosas, le recomiendo que compare con lo mejor que podría hacer usando código nativo (o incluso un script de línea de comando que hace aproximadamente lo mismo). Decir que se tarda una hora en atravesar una estructura de red no significa mucho. Decirnos que puedes hacerlo de forma nativa en 7 segundos, pero que en Java lleva una hora llamará la atención de la gente.

Kevin Day
fuente
3
Java 7 ya está ahí, por lo que sería útil un ejemplo sobre cómo hacerlo en Java 7. O al menos un enlace. O un nombre de clase para buscar en Google. - esto es «stackoverflow» y no «cs teóricos» después de todo ;-).
Martin
3
bueno, veamos ... Mi publicación original fue en marzo de 2010 ... Ahora es enero de 2012 ... Y acabo de revisar el historial de inventario de mi equipo, y no me veo a mí mismo teniendo una máquina del tiempo en marzo de 2010, así que creo que probablemente esté justificado para responder sin dar un ejemplo explícito ;-)
Kevin Day
7

esto funcionará bien ... y es recursivo

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
fuente
1
Buena respuesta si quieres algo que funcione con java <7.
ssimm
3

Personalmente, me gusta esta versión de FileUtils. Aquí hay un ejemplo que encuentra todos los mp3 o flacs en un directorio o en cualquiera de sus subdirectorios:

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

Esto funcionará bien

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

Mam's
fuente
Bienvenido a StackOverflow Mam's, ¿podría aclarar cómo su respuesta es una mejora o alternativa a las muchas respuestas existentes?
Lilienthal
1

Esta función probablemente enumerará todo el nombre del archivo y su ruta desde su directorio y sus subdirectorios.

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
fuente
1
Este ejemplo no tiene en cuenta el hecho de que el método listFiles (), puede y devolverá nulo. 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);
        }


    }
Niraj Sonawane
fuente
0

se siente como si fuera estúpido acceder al sistema de archivos y obtener el contenido de cada subdirectorio en lugar de obtener todo a la vez.

Tu sentimiento está mal. Así es como funcionan los sistemas de archivos. No hay una forma más rápida (excepto cuando tiene que hacer esto repetidamente o para diferentes patrones, puede almacenar en caché todas las rutas de archivo en la memoria, pero luego debe lidiar con la invalidación de caché, es decir, lo que sucede cuando los archivos se agregan / eliminan / renombran la aplicación se ejecuta).

Michael Borgwardt
fuente
La cosa es que quiero cargar todos los archivos de un cierto tipo con un cierto formato de nombre en una biblioteca que se presenta al usuario y cada vez que se inicia la aplicación, se supone que la biblioteca se actualiza, pero se tarda una eternidad en actualizar la biblioteca. La única solución que obtuve es ejecutar la actualización en segundo plano, pero sigue siendo molesto que lleve tanto tiempo hasta que se carguen todos los archivos nuevos. Debe haber una mejor manera de hacerlo. O al menos una forma mejor de actualizar la base de datos. Se siente estúpido que revise todos los archivos por los que ya pasó una vez. ¿Hay alguna forma de buscar actualizaciones solo rápidamente?
Hultner
@Hultner: Java 7 incluirá una función para recibir notificaciones de actualizaciones del sistema de archivos, pero eso solo funcionaría mientras la aplicación se está ejecutando, por lo que, a menos que desee tener un servicio en segundo plano ejecutándose todo el tiempo, no sería de ayuda. Puede haber problemas especiales con los recursos compartidos de red, como describe Kevin, pero siempre que dependa de escanear todo el árbol de directorios, realmente no hay mejor manera.
Michael Borgwardt
Quizás pueda crear algunos archivos de índice. Si hay una forma de verificar el tamaño del directorio, simplemente puede buscar archivos nuevos cuando cambie el tamaño.
James P.
@James: no hay forma de verificar el tamaño del directorio. El tamaño de un directorio se obtiene obteniendo el tamaño de cada archivo y sumándolos, en todos los sistemas de archivos que conozco. En realidad, la pregunta "¿cuál es el tamaño de este directorio?" ni siquiera necesariamente tiene sentido si considera los enlaces duros.
Michael Borgwardt
Tienes razón. Sigo sintiendo que un poco de almacenamiento en caché y / o huellas digitales podría acelerar el proceso.
James P.
0

Para que sepas, isDirectory () es un método bastante lento. Lo encuentro bastante lento en mi navegador de archivos. Buscaré en una biblioteca para reemplazarla con código nativo.

Daniel Ryan
fuente
0

La forma más eficiente que encontré al tratar con millones de carpetas y archivos es capturar la lista de directorios a través del comando DOS en algún archivo y analizarlo. Una vez que haya analizado los datos, podrá realizar análisis y calcular estadísticas.

Kiran
fuente
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
fuente
Por favor, agregue también alguna explicación.
d4Rk
0

En Guava no tiene que esperar a que se le devuelva una colección, sino que puede iterar sobre los archivos. Es fácil imaginar una IDoSomethingWithThisFileinterfaz en la firma de la siguiente función:

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 también le permite entre varios estilos de recorrido.

Marco Junio ​​Bruto
fuente
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
fuente
0

Otro código optimizado

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
fuente
Por favor, ¿puede ampliar su respuesta con una explicación más detallada? Esto será muy útil para comprender. ¡Gracias!
vezunchik
0

Un ejemplo más de listar archivos y directorios usando 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
fuente