2010-03-28 29 views
63

que tienen 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.Lista de todos los archivos de un directorio de forma recursiva con Java

Mi plan es la primera carga todos los archivos del directorio recursivamente y luego, después de que ir a través de todos los archivos con la expresión regular para filtrar todos los archivos que no quiero. ¿Alguien tiene una mejor sugerencia?

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 sólo una prueba más adelante no voy a utilizar el código de esta forma, en vez voy a añadir la ruta y la fecha de modificación de cada archivo que coincide con una expresión regular avanzada para una matriz.

+1

... ¿cuál es la pregunta? ¿Estás buscando la validación de que este código funcionará? –

+0

No, sé que este código funciona, pero es muy lento y se siente como si fuera un estúpido acceso al sistema de archivos y obtener los contenidos para cada subdirectorio en lugar de obtener todo a la vez. – Hultner

+1

posible duplicado de [Lista recursiva de archivos en Java] (http://stackoverflow.com/questions/2056221/recursively-list-files-in-java) – Luv

Respuesta

120

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

Por ejemplo, para su expresión regular:

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

Esto hará de forma recursiva buscar archivos que coinciden con la expresión regular ^(.*?), la devolución de los resultados como una colección.

Vale la pena señalar que esto no más rápido que estar rodando su propio código, que está haciendo la misma cosa - pesca de arrastre de un sistema de archivos en Java es simplemente lento. La diferencia es que la versión de Apache Commons no tendrá errores.

+0

Miré allí y de eso usaría http: // commons. apache.org/io/api-release/index.html?org/apache/commons/io/FileUtils.html para obtener todo el archivo del directorio y subdirectorios y luego buscar a través de los archivos para que coincidan con mi expresión regular. ¿O estoy equivocado? – Hultner

+0

Sí, cuesta más de una hora escanear la carpeta y hacer eso 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í, sería una diferencia significativa? Por ahora, cambié el código en la línea if isdir y lo agregué para que el directorio también tuviera que coincidir con una expresión regular que se incluiría en la búsqueda. Veo que en su ejemplo dice DirectoryFileFilter.DIRECTORY, supongo que podría tener un filtro regex allí. – Hultner

+1

escribirlo usando llamadas nativas lo haría más rápido: FindFirstFile/FineNextFile le permite consultar los atributos del archivo sin tener que hacer una llamada por separado, esto puede tener implicaciones masivas para redes de latencia más altas. El enfoque de Java para esto es terriblemente ineficiente. –

0

se siente como si fuera estúpida acceso al sistema de archivos y obtener el contenido de cada subdirectorio en vez de conseguir todo a la vez.

Su sensación es erróneo. 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 archivos en la memoria, pero luego tiene que lidiar con la invalidación de caché, es decir, qué sucede cuando se agregan/eliminan/renombran archivos mientras la aplicación se ejecuta).

+0

La cosa es I desea cargar todos los archivos de un cierto tipo con un determinado 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 debe actualizarse, pero lleva tiempo 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 mejor forma de actualizar la base de datos. Se siente estúpido que revise todos los archivos que ya ha pasado. ¿Hay alguna manera de encontrar solo actualizaciones rápidamente? – Hultner

+0

@Hultner: Java 7 incluirá una instalación para recibir notificaciones sobre las actualizaciones del sistema de archivos, pero eso solo funcionará mientras la aplicación se está ejecutando, por lo que a menos que desee tener un servicio en segundo plano todo el tiempo, no sería de ayuda. Puede haber problemas especiales con los recursos compartidos de red como describe Kevin, pero mientras dependa de escanear todo el árbol de directorios, realmente no hay mejor manera. –

+0

Quizás podría crear algunos archivos de índice. Si hay una forma de verificar el tamaño del directorio, puede escanear los archivos nuevos cuando cambie el tamaño. –

12

interfaz de Java para leer contenido de la carpeta de sistema de archivos no es de buen calidad (como usted ha descubierto). JDK 7 soluciona esto con una interfaz completamente nueva para este tipo de cosas, que debería llevar el rendimiento nativo a este tipo de operaciones.

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

Si no puede esperar para JDK7, una estrategia para abordar esta latencia es utilizar varios subprocesos y utilizar un ExecutorService con un máximo de # subprocesos para realizar su recursión. 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 hacerlo con un solo hilo.

En todas sus discusiones sobre este tipo de cosas, le recomiendo que lo compare con lo mejor que podría hacer usando código nativo (o incluso un script de línea de comandos que hace más o menos lo mismo). Decir que lleva una hora atravesar una estructura de red en realidad no significa mucho. Decirnos que puedes hacerlo nativo en 7 segundos, pero lleva una hora en Java atraerá la atención de las personas.

+3

Java 7 ya está allí, por lo que un ejemplo de cómo hacerlo en Java 7 sería útil. O al menos un enlace. O un nombre de clase para buscar en google. - esto es «stackoverflow» y no «teórico cs» después de todo ;-). – Martin

+3

bien vamos a ver ...Mi publicación original fue en marzo de 2010 ... Ahora es enero de 2012 ... Y acabo de consultar el historial de inventario de mi equipo, y no veo que haya tenido una máquina del tiempo en marzo de 2010, así que creo que estoy probablemente esté justificado al responder sin dar un ejemplo explícito ;-) –

+4

@Martin [Estos son los documentos que está buscando]] (http://docs.oracle.com/javase/7/docs/api/java/nio/file) /Files.html#walkFileTree%28java.nio.file.Path,%20java.util.Set,%20int,%20java.nio.file.FileVisitor%29) – trutheality

0

Para que lo sepas isDirectory() es un método bastante lento. Lo encuentro bastante lento en mi buscador de archivos. Voy a buscar en una biblioteca para reemplazarlo con código nativo.

0

La manera 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, puede hacer análisis y calcular estadísticas.

1

Esta función probablemente listará 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]); 
     } 

    } 

} 
+1

Este ejemplo no tiene en cuenta el hecho de que listFiles() método, puede y devolverá nulo. http://docs.oracle.com/javase/7/docs/api/java/io/File.html#listFiles() –

12

La forma más rápida de obtener el contenido de un directorio utilizando 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(); 
+2

Agradable, pero solo obtiene archivos para un directorio. Si quiere ver todos los subdirectorios, vea mi respuesta alternativa. – Dan

+2

'Files.newDirectoryStream' puede lanzar una IOException. Sugiero ajustar esa línea en Java7 try-with-statement para que la transmisión siempre esté cerrada para usted (excepción o no, sin la necesidad de un 'finally'). Vea también aquí: http://stackoverflow.com/questions/17739362/java7-try-with-resources-statement-advantage – Greg

16

Con Java 7 una manera más rápida que caminar a través de un árbol de directorios se introdujo con la funcionalidad Paths y Files. Son mucho más rápidos que el "viejo" File manera.

Este sería el código para caminar a través y comprobar los nombres de ruta 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; 
     } 
    }); 
} 
+5

Nice answer :), también hay una clase implementada de él llamada "SimpleFileVisitor", si no lo hace necesita todas las funcionalidades implementadas, puede simplemente anular las funciones necesarias. – GalDude33

22

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

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

esto va a funcionar muy bien ... y su recursiva

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 
     } 
    } 
} 
+1

Buena respuesta si quieres algo que funcione con java <7. – ssimm

2

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

+0

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

3

personalmente me gusta esta versión de FileUtils. Aquí hay un ejemplo que encuentra todos los archivos MP3 o FLACS en un directorio o cualquiera de sus subdirectorios:

String[] types = {"mp3", "flac"}; 
Collection<File> files2 = FileUtils.listFiles(/path/to/your/dir, types , true); 
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); 
    } 
} 
+0

Por favor, agregue algunas explicaciones también. – d4Rk

41

En Java 8, que es un 1-liner a través de Files.find() con un arbitrariamente grande de profundidad (por ejemplo 999) y BasicFileAttributes de isRegularFile()

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

para añadir más filtrado, mejorar 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 
+0

Sugiero usar siempre los métodos de Archivos que devuelven Stream en bloques try-with-resources: de lo contrario, mantendrá el recurso abierto –

0

En Guava no tiene que esperar que se le devuelva una colección, pero puede iterar sobre los archivos.Es fácil imaginar una interfaz IDoSomethingWithThisFile en la firma de la función a continuació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 a entre varios estilos de recorrido.

Cuestiones relacionadas