write read files example create java file file-io out-of-memory

read - Cómo listar un directorio de 2 millones de archivos en Java sin tener una excepción de "memoria insuficiente"



read file java example (15)

¿Por qué almacena 2 millones de archivos en el mismo directorio de todos modos? Me imagino que ya ralentiza el acceso terriblemente en el nivel del sistema operativo.

Definitivamente me gustaría tenerlos divididos en subdirectorios (por ejemplo, por fecha / hora de creación) ya antes del procesamiento. Pero si no es posible por alguna razón, ¿podría hacerse durante el procesamiento? Por ejemplo, mueva 1000 archivos en cola para Process1 en Directory1, otros 1000 archivos para Process2 en Directory2, etc. Luego, cada proceso / thread ve solo el (número limitado de) archivos divididos en porciones para él.

Tengo que lidiar con un directorio de aproximadamente 2 millones de xml para procesar.

Ya he resuelto el proceso de distribución del trabajo entre máquinas e hilos usando colas y todo va bien.

Pero ahora el gran problema es el cuello de botella de leer el directorio con los 2 millones de archivos para llenar las colas de forma incremental.

Intenté usar el método File.listFiles() , pero me da una java out of memory: heap space excepción de out of memory: heap space . ¿Algunas ideas?


Antes que nada, ¿tienes alguna posibilidad de usar Java 7? Ahí tienes un FileVisitor y Files.walkFileTree , que probablemente debería funcionar dentro de tus limitaciones de memoria.

De lo contrario, la única forma en que puedo pensar es usar File.listFiles(FileFilter filter) con un filtro que siempre devuelve false (asegurando que la matriz completa de archivos nunca se guarde en la memoria), pero que capture los archivos para ser procesados ​​junto con el camino, y tal vez los pone en una cola de productor / consumidor o escribe los nombres de archivo en el disco para su posterior recorrido.

Alternativamente, si controla los nombres de los archivos, o si reciben un nombre de alguna manera agradable, puede procesar los archivos en fragmentos utilizando un filtro que acepte nombres de archivo en el archivo de file0000000 - filefile0001000 luego file0001000 - filefile0002000 y así sucesivamente.

Si los nombres no reciben un nombre como este, puedes intentar filtrarlos según el código hash del nombre de archivo, que se supone que se distribuye bastante uniformemente en el conjunto de enteros.

Actualización: suspiro. Probablemente no funcionará. Solo eché un vistazo a la implementación de listFiles:

public File[] listFiles(FilenameFilter filter) { String ss[] = list(); if (ss == null) return null; ArrayList v = new ArrayList(); for (int i = 0 ; i < ss.length ; i++) { if ((filter == null) || filter.accept(this, ss[i])) { v.add(new File(ss[i], this)); } } return (File[])(v.toArray(new File[v.size()])); }

por lo que probablemente fallará en la primera línea de todos modos ... Un poco decepcionante. Creo que su mejor opción es colocar los archivos en diferentes directorios.

Por cierto, ¿podría dar un ejemplo de un nombre de archivo? ¿Son "adivinables"? Me gusta

for (int i = 0; i < 100000; i++) tryToOpen(String.format("file%05d", i))


En primer lugar, podría intentar aumentar la memoria de su JVM con el paso -Xmx1024m, por ejemplo


Prueba esto, me funciona, pero no tenía tantos documentos ...

File dir = new File("directory"); String[] children = dir.list(); if (children == null) { //Either dir does not exist or is not a directory System.out.print("Directory doesn''t exist/n"); } else { for (int i=0; i<children.length; i++) { // Get filename of file or directory String filename = children[i]; }


Publique la traza de pila completa de la excepción OOM para identificar dónde se encuentra el cuello de botella, así como un programa Java breve y completo que muestre el comportamiento que ve.

Lo más probable es que recopile los dos millones de entradas en la memoria y no quepan. ¿Se puede aumentar el espacio de montón?


Si los nombres de archivo siguen ciertas reglas, puede usar File.list(filter) lugar de File.listFiles para obtener partes manejables de la lista de archivos.


Use File.list() lugar de File.listFiles() : los objetos String que devuelve consumen menos memoria que los objetos File , y (más importante aún, según la ubicación del directorio) no contienen el nombre completo de la ruta.

A continuación, construya objetos File según sea necesario al procesar el resultado.

Sin embargo, esto tampoco funcionará para directorios arbitrariamente grandes. Es una mejor idea general organizar sus archivos en una jerarquía de directorios para que ningún directorio tenga más de unos miles de entradas.


Como está en Windows, parece que debería haber usado ProcessBuilder para comenzar algo así como "cmd / k dir / b target_directory", capturar el resultado de eso y enrutarlo a un archivo. A continuación, puede procesar ese archivo una línea a la vez, leyendo los nombres de los archivos y tratando con ellos.

¿Mejor tarde que nunca? ;)


Si Java 7 no es una opción, este truco funcionará (para UNIX):

Process process = Runtime.getRuntime().exec(new String[]{"ls", "-f", "/path"}); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while (null != (line = reader.readLine())) { if (line.startsWith(".")) continue; System.out.println(line); }

El parámetro -f lo acelerará (desde man ls ):

-f do not sort, enable -aU, disable -lst


En caso de que pueda usar Java 7, esto se puede hacer de esta manera y no tendrá esos problemas de memoria.

Path path = FileSystems.getDefault().getPath("C://path//with//lots//of//files"); Files.walkFileTree(path, new FileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { // here you have the files to process System.out.println(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.TERMINATE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } });


Puede usar listFiles con un FilenameFilter especial. La primera vez que FilenameFilter se envía a listFiles, acepta los primeros 1000 archivos y luego los guarda como visitados.

La próxima vez que FilenameFilter se envíe a listFiles, ignorará los primeros 1000 archivos visitados y devolverá los siguientes 1000, y así sucesivamente hasta que se complete.


Puede hacerlo con la biblioteca Apache FileUtils. Sin problema de memoria Lo revisé con visualvm.

Iterator<File> it = FileUtils.iterateFiles(folder, null, true); while (it.hasNext()) { File fileEntry = (File) it.next(); }

Espero que ayude. adiós


Como primer enfoque, puede tratar de modificar algunas configuraciones de memoria JVM, por ejemplo, aumentar el tamaño del almacenamiento dinámico tal como se sugirió o incluso usar la opción AggressiveHeap. Teniendo en cuenta la gran cantidad de archivos, puede que esto no ayude, entonces sugeriría solucionar el problema. Cree varios archivos con nombres de archivo en cada uno, digamos 500k nombres de archivo por archivo y lea de ellos.


Me enfrenté al mismo problema cuando desarrollé una aplicación de escaneo de malware. Mi solución es ejecutar el comando de shell para listar todos los archivos. Es más rápido que los métodos recursivos navegar por carpeta por carpeta.

vea más sobre el comando de shell aquí: http://adbshell.com/commands/adb-shell-ls

Process process = Runtime.getRuntime().exec("ls -R /"); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); //TODO: Read the stream to get a list of file path.


Esto también requiere Java 7, pero es más simple que la respuesta Files.walkFileTree si solo desea listar el contenido de un directorio y no recorrer todo el árbol:

Path dir = Paths.get("/some/directory"); try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { for (Path path : stream) { handleFile(path.toFile()); } } catch (IOException e) { handleException(e); }

La implementación de DirectoryStream es específica de la plataforma y nunca llama a File.list ni a nada parecido, en su lugar usa las llamadas al sistema Unix o Windows que iteran sobre un directorio una entrada a la vez.