2012-03-28 21 views
7

Estoy buscando una manera eficiente para repetir miles de archivos en uno o más directorios.forma eficiente de iterar sobre la lista de archivos

La única manera de iterar sobre los archivos en un directorio parece ser File.list*() funciones. Estas funciones cargan efectivamente toda la lista de archivos en algún tipo de colección y luego permiten que el usuario itere sobre ella. Esto parece ser poco práctico en términos de consumo de tiempo/memoria. Traté de mirar commons-io y otras herramientas similares. pero todos finalmente llaman al File.list*() en algún lugar dentro. JDK7's walkFileTree() estuvo cerca, pero no tengo control sobre cuándo elegir el siguiente elemento.

Tengo más de 150.000 archivos en un directorio y después de muchos -Xms/ejecuciones de prueba -Xmm que se deshizo de los problemas de desbordamiento de memoria. Pero el tiempo que toma llenar el conjunto no ha cambiado.

deseo de hacer algún tipo de una clase que utiliza Iterable opendir()/closedir() como funciones para cargar con pereza nombres de archivo según sea necesario. ¿Hay alguna forma de hacer esto?

Actualización:

Java 7 NIO.2 apoya iteración de archivos a través java.nio.file.DirectoryStream. Es una clase Iterable. En cuanto a JDK6 y siguientes, la única opción son los métodos File.list*().

+0

No sé si existe una solución estándar para eso. Supongo que no hay otra manera de hacerlo, pero impleméntelo solo en C y acceda a él a través de JNI ... –

+0

Las respuestas en esta pregunta pueden ser útiles: http://stackoverflow.com/questions/1034977/how- para-recuperar-una-lista-de-directorios-rápidamente-en-java – charlemagne

+0

Sospecho que el verdadero problema aquí es que usted tiene un solo directorio con 150K archivos. Ciertamente no me gustaría poner a prueba un sistema de archivos de esa manera. ¿No puedes usar subdirectorios, quizás agrupar archivos por los dos primeros caracteres en el nombre del archivo o algo así? –

Respuesta

3

Aquí es un ejemplo de cómo iterar sobre las entradas de directorio sin tener que almacenar 159k de ellos en una matriz. Agregue el manejo de error/excepción/apagado/tiempo de espera según sea necesario. Esta técnica utiliza una secuencia secundaria para cargar una pequeña cola de bloqueo.

uso es:

FileWalker z = new FileWalker(new File("\\"), 1024); // start path, queue size 
Iterator<Path> i = z.iterator(); 
while (i.hasNext()) { 
    Path p = i.next(); 
} 

El ejemplo:

public class FileWalker implements Iterator<Path> { 
    final BlockingQueue<Path> bq; 
    FileWalker(final File fileStart, final int size) throws Exception { 
    bq = new ArrayBlockingQueue<Path>(size); 
    Thread thread = new Thread(new Runnable() { 
    public void run() { 
     try { 
     Files.walkFileTree(fileStart.toPath(), new FileVisitor<Path>() { 
      public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 
      return FileVisitResult.CONTINUE; 
      } 
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 
      try { 
       bq.offer(file, 4242, TimeUnit.HOURS); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
      return FileVisitResult.CONTINUE; 
      } 
      public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 
      return FileVisitResult.CONTINUE; 
      } 
      public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 
      return FileVisitResult.CONTINUE; 
      } 
     }); 
     } catch (IOException e) { 
     e.printStackTrace(); 
     } 
    } 
    }); 
    thread.setDaemon(true); 
    thread.start(); 
    thread.join(200); 
} 
public Iterator<Path> iterator() { 
    return this; 
} 
public boolean hasNext() { 
    boolean hasNext = false; 
    long dropDeadMS = System.currentTimeMillis() + 2000; 
    while (System.currentTimeMillis() < dropDeadMS) { 
    if (bq.peek() != null) { 
     hasNext = true; 
     break; 
    } 
    try { 
     Thread.sleep(1); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 
    } 
    return hasNext; 
} 
public Path next() { 
    Path path = null; 
    try { 
    path = bq.take(); 
    } catch (InterruptedException e) { 
    e.printStackTrace(); 
    } 
    return path; 
} 
public void remove() { 
    throw new UnsupportedOperationException(); 
} 
} 
+0

¡Gracias! El hilo adicional pa Es un poco molesto, pero encontraré una manera de impulsar este Runnable en algún hilo de drones. –

+0

@Eshan - Precio menor a pagar ya que muere. Pero tenga en cuenta que seguirá vivo si su tiempo (hasNext()) termina antes de tiempo. Necesita agregar algún código de seguridad tal como lo ha notado. Pero esta técnica mantiene el uso de memoria muy muy bajo. – Java42

0

¿Puede agrupar sus cargas por tipos de archivo para reducir los lotes?

+0

La división de archivos en grupos en todos los directorios suena bien. Intenté esto en uno de los sitios de mis usuarios y resultó que llenaron miles de archivos en dos directorios uno 'a-z' y el otro '0-9'. Como dije en otro comentario, es más fácil corregir el código que pedirle a los usuarios que cambien la forma en que trabajan :) –

1

Esto parece ser poco práctico en términos de tiempo/consumo de memoria.

Incluso el archivo 150,000 no consumirá una cantidad de memoria poco práctica.

Deseo hacer algún tipo de clase Iterable que use funciones como opendir()/closedir() para cargar los nombres de los archivos según sea necesario. ¿Hay alguna forma de hacer esto?

Necesitará escribir o buscar una biblioteca de códigos nativos para acceder a esas funciones de C. Probablemente va a introducir más problemas de los que resuelve. Mi consejo sería simplemente usar File.list() y aumentar el tamaño del montón.


En realidad, hay otra alternativa hacky. Use System.exec para ejecutar el comando ls (o el equivalente de Windows) y escriba su iterador para leer y analizar el texto de salida del comando. Eso evita la maldad asociada con el uso de bibliotecas nativas de Java.

+0

El software, cuando fue diseñado hace 15 años, cometió un error al bifurcar hilos para hacer cosas que el diseñador percibe como "paralelo" . La versión actual de hoy teje más de 100 hilos utiliza 1.5 GiB + memoria para entrar en funcionamiento en JDK 6. La lista del directorio simplemente agrega más a eso. A eso me refería cuando dije que no era práctico. JNI/System.exec() no es una opción aquí. –

+0

* "JNI/System.exec() no es una opción aquí" *. Entonces estás sin opciones usando Java 6. Lo siento. –

+1

* "El software, cuando se diseñó hace 15 años, cometió un error al bifurcar hilos para hacer cosas, lo que el diseñador entonces consideró como" paralelo ". Parece que necesitas arreglar ESE problema primero. De hecho, dado que JNI y el ejecutivo no son opciones, es probable que no tenga otra opción. Pero la buena noticia es que probablemente puedas reemplazar el desenfrenado hilo bifurcando mediante la reestructuración para usar un servicio de ejecutor de grupo de hilos delimitados y así eliminar la sobrecarga de memoria de más de 90 pilas de hilos, etc. –

0

Me preguntaba por qué un método() que devuelve la normalidad file.list String [] de nombres de archivos (no las file.listFiles()) que consume gran cantidad de memoria? Es una llamada nativa que solo devuelve el nombre de los archivos. Probablemente puede iterar sobre él y cargar de forma lenta cualquier objeto de archivo que necesite.

+0

Malo. Es un error tipográfico. debería haber sido 'File.list()'. –

+0

Eso es exactamente lo que Charles escribió en su respuesta. –

+0

lo siento ... ¿qué? – Kshitij

Cuestiones relacionadas