Java obtiene el tamaño del archivo de manera eficiente

Mientras busca en Google, veo que usar java.io.File#length() puede ser lento. FileChannel tiene un método de size() que también está disponible.

¿Hay una forma eficiente en Java para obtener el tamaño del archivo?

Bueno, traté de medirlo con el siguiente código:

Para ejecuciones = 1 e iteraciones = 1, el método de URL es el más rápido seguido por el canal. Corro esto con una pausa fresca unas 10 veces. Entonces, para acceder una vez, usar la URL es la manera más rápida que puedo pensar:

 LENGTH sum: 10626, per Iteration: 10626.0 CHANNEL sum: 5535, per Iteration: 5535.0 URL sum: 660, per Iteration: 660.0 

Para ejecuciones = 5 e iteraciones = 50, la imagen dibuja diferente.

 LENGTH sum: 39496, per Iteration: 157.984 CHANNEL sum: 74261, per Iteration: 297.044 URL sum: 95534, per Iteration: 382.136 

El archivo debe almacenar en caché las llamadas al sistema de archivos, mientras que los canales y la URL tienen cierta sobrecarga.

Código:

 import java.io.*; import java.net.*; import java.util.*; public enum FileSizeBench { LENGTH { @Override public long getResult() throws Exception { File me = new File(FileSizeBench.class.getResource( "FileSizeBench.class").getFile()); return me.length(); } }, CHANNEL { @Override public long getResult() throws Exception { FileInputStream fis = null; try { File me = new File(FileSizeBench.class.getResource( "FileSizeBench.class").getFile()); fis = new FileInputStream(me); return fis.getChannel().size(); } finally { fis.close(); } } }, URL { @Override public long getResult() throws Exception { InputStream stream = null; try { URL url = FileSizeBench.class .getResource("FileSizeBench.class"); stream = url.openStream(); return stream.available(); } finally { stream.close(); } } }; public abstract long getResult() throws Exception; public static void main(String[] args) throws Exception { int runs = 5; int iterations = 50; EnumMap durations = new EnumMap(FileSizeBench.class); for (int i = 0; i < runs; i++) { for (FileSizeBench test : values()) { if (!durations.containsKey(test)) { durations.put(test, 0l); } long duration = testNow(test, iterations); durations.put(test, durations.get(test) + duration); // System.out.println(test + " took: " + duration + ", per iteration: " + ((double)duration / (double)iterations)); } } for (Map.Entry entry : durations.entrySet()) { System.out.println(); System.out.println(entry.getKey() + " sum: " + entry.getValue() + ", per Iteration: " + ((double)entry.getValue() / (double)(runs * iterations))); } } private static long testNow(FileSizeBench test, int iterations) throws Exception { long result = -1; long before = System.nanoTime(); for (int i = 0; i < iterations; i++) { if (result == -1) { result = test.getResult(); //System.out.println(result); } else if ((result = test.getResult()) != result) { throw new Exception("variance detected!"); } } return (System.nanoTime() - before) / 1000; } } 

El punto de referencia dado por GHad mide muchas otras cosas (como reflexión, creación de instancias de objetos, etc.) además de obtener la longitud. Si tratamos de deshacernos de estas cosas, en una llamada recibo los siguientes momentos en microsegundos:

    sum de archivo ___ 19.0, por iteración ___ 19.0
     raf sum ___ 16.0, por iteración ___ 16.0
 canal sum__273.0, por iteración__273.0

Para 100 ejecuciones y 10000 iteraciones, obtengo:

    archivo sum__1767629.0, por iteración__1.7676290000000001
     raf sum ___ 881284.0, por iteración__0.8812840000000001
 cantidad de canal ___ 414286.0, por iteración__0.414286

Ejecuté el siguiente código modificado dando como argumento el nombre de un archivo de 100MB.

 import java.io.*; import java.nio.channels.*; import java.net.*; import java.util.*; public class FileSizeBench { private static File file; private static FileChannel channel; private static RandomAccessFile raf; public static void main(String[] args) throws Exception { int runs = 1; int iterations = 1; file = new File(args[0]); channel = new FileInputStream(args[0]).getChannel(); raf = new RandomAccessFile(args[0], "r"); HashMap times = new HashMap(); times.put("file", 0.0); times.put("channel", 0.0); times.put("raf", 0.0); long start; for (int i = 0; i < runs; ++i) { long l = file.length(); start = System.nanoTime(); for (int j = 0; j < iterations; ++j) if (l != file.length()) throw new Exception(); times.put("file", times.get("file") + System.nanoTime() - start); start = System.nanoTime(); for (int j = 0; j < iterations; ++j) if (l != channel.size()) throw new Exception(); times.put("channel", times.get("channel") + System.nanoTime() - start); start = System.nanoTime(); for (int j = 0; j < iterations; ++j) if (l != raf.length()) throw new Exception(); times.put("raf", times.get("raf") + System.nanoTime() - start); } for (Map.Entry entry : times.entrySet()) { System.out.println( entry.getKey() + " sum: " + 1e-3 * entry.getValue() + ", per Iteration: " + (1e-3 * entry.getValue() / runs / iterations)); } } } 

Todos los casos de prueba en esta publicación tienen fallas ya que acceden al mismo archivo para cada método probado. Por lo tanto, el almacenamiento en caché de disco se inicia con las pruebas 2 y 3. Para probar mi punto, tomé el caso de prueba proporcionado por GHAD y cambié el orden de enumeración y a continuación se muestran los resultados.

En cuanto al resultado, creo que File.length () es el ganador realmente.

El orden de prueba es el orden de salida. Incluso puede ver el tiempo empleado en mi máquina variada entre ejecuciones, pero con File.Length () cuando no es el primero, y con el primer acceso de disco ganado.

 --- LENGTH sum: 1163351, per Iteration: 4653.404 CHANNEL sum: 1094598, per Iteration: 4378.392 URL sum: 739691, per Iteration: 2958.764 --- CHANNEL sum: 845804, per Iteration: 3383.216 URL sum: 531334, per Iteration: 2125.336 LENGTH sum: 318413, per Iteration: 1273.652 --- URL sum: 137368, per Iteration: 549.472 LENGTH sum: 18677, per Iteration: 74.708 CHANNEL sum: 142125, per Iteration: 568.5 

Cuando modifico su código para usar un archivo al que se accede por una ruta absoluta en lugar de un recurso, obtengo un resultado diferente (para 1 ejecución, 1 iteración y un archivo de 100.000 bytes; las horas para un archivo de 10 bytes son idénticas a 100.000 bytes) )

LONGITUD sum: 33, por iteración: 33.0

CANAL sum: 3626, por iteración: 3626.0

Suma de URL: 294, por iteración: 294.0

En respuesta al punto de referencia de rgrig, el tiempo necesario para abrir / cerrar las instancias FileChannel y RandomAccessFile también debe tenerse en cuenta, ya que estas clases abrirán una secuencia para leer el archivo.

Después de modificar el punto de referencia, obtuve estos resultados para 1 iteraciones en un archivo de 85 MB:

 file totalTime: 48000 (48 us) raf totalTime: 261000 (261 us) channel totalTime: 7020000 (7 ms) 

Para 10000 iteraciones en el mismo archivo:

 file totalTime: 80074000 (80 ms) raf totalTime: 295417000 (295 ms) channel totalTime: 368239000 (368 ms) 

Si todo lo que necesita es el tamaño del archivo, file.length () es la forma más rápida de hacerlo. Si planea usar el archivo para otros fines, como leer / escribir, entonces RAF parece ser una mejor opción. Solo no olvides cerrar la conexión del archivo 🙂

 import java.io.File; import java.io.FileInputStream; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.util.HashMap; import java.util.Map; public class FileSizeBench { public static void main(String[] args) throws Exception { int iterations = 1; String fileEntry = args[0]; Map times = new HashMap(); times.put("file", 0L); times.put("channel", 0L); times.put("raf", 0L); long fileSize; long start; long end; File f1; FileChannel channel; RandomAccessFile raf; for (int i = 0; i < iterations; i++) { // file.length() start = System.nanoTime(); f1 = new File(fileEntry); fileSize = f1.length(); end = System.nanoTime(); times.put("file", times.get("file") + end - start); // channel.size() start = System.nanoTime(); channel = new FileInputStream(fileEntry).getChannel(); fileSize = channel.size(); channel.close(); end = System.nanoTime(); times.put("channel", times.get("channel") + end - start); // raf.length() start = System.nanoTime(); raf = new RandomAccessFile(fileEntry, "r"); fileSize = raf.length(); raf.close(); end = System.nanoTime(); times.put("raf", times.get("raf") + end - start); } for (Map.Entry entry : times.entrySet()) { System.out.println(entry.getKey() + " totalTime: " + entry.getValue() + " (" + getTime(entry.getValue()) + ")"); } } public static String getTime(Long timeTaken) { if (timeTaken < 1000) { return timeTaken + " ns"; } else if (timeTaken < (1000*1000)) { return timeTaken/1000 + " us"; } else { return timeTaken/(1000*1000) + " ms"; } } } 

Me encontré con este mismo problema. Necesitaba obtener el tamaño de archivo y la fecha de modificación de 90,000 archivos en un recurso compartido de red. Usar Java, y ser lo más minimalista posible, tomaría mucho tiempo. (Necesitaba obtener la URL del archivo y la ruta del objeto, así que es algo variado, pero más de una hora). Luego usé un ejecutable nativo de Win32 e hice la misma tarea, solo volcando el archivo ruta, modificado y tamaño a la consola, y ejecutado desde Java. La velocidad fue increíble. El proceso nativo y mi manejo de cadenas para leer los datos podría procesar más de 1000 elementos por segundo.

Entonces, aunque la gente no calificó el comentario anterior, esta es una solución válida y resolvió mi problema. En mi caso, sabía las carpetas que necesitaba con los tamaños de antemano, y podía pasar eso en la línea de comandos a mi aplicación win32. Pasé horas para procesar un directorio a minutos.

El problema también parecía ser específico de Windows. OS X no tenía el mismo problema y podía acceder a la información del archivo de red tan rápido como el sistema operativo podía hacerlo.

El manejo de archivos Java en Windows es terrible. Sin embargo, el acceso al disco local para archivos está bien. Solo las acciones de red causaron el terrible rendimiento. Windows podría obtener información sobre el recurso compartido de red y calcular el tamaño total en menos de un minuto también.

–Ben

Si desea el tamaño de archivo de varios archivos en un directorio, use Files.walkFileTree . Puede obtener el tamaño de los BasicFileAttributes que recibirá.

Esto es mucho más rápido que llamar a .length() en el resultado de File.listFiles() o usar Files.size() en el resultado de Files.newDirectoryStream() . En mis casos de prueba, fue aproximadamente 100 veces más rápido.

En realidad, creo que la “ls” puede ser más rápida. Definitivamente hay algunos problemas en Java relacionados con la obtención de información de archivo. Lamentablemente, no existe un método seguro equivalente de l recursivo para Windows. (El DIR / S de cmd.exe puede confundirse y generar errores en bucles infinitos)

En XP, al acceder a un servidor en la LAN, me lleva 5 segundos en Windows obtener el conteo de los archivos en una carpeta (33,000) y el tamaño total.

Cuando repito recursivamente a través de esto en Java, me lleva más de 5 minutos. Comencé a medir el tiempo que toma hacer file.length (), file.lastModified () y file.toURI () y lo que encontré es que el 99% de mi tiempo lo toman esas 3 llamadas. Las 3 llamadas que realmente necesito hacer …

La diferencia para 1000 archivos es 15 ms local frente a 1800 ms en el servidor. La exploración de la ruta del servidor en Java es ridículamente lenta. Si el SO nativo puede escanear rápidamente esa misma carpeta, ¿por qué no puede Java?

Como una prueba más completa, utilicé WineMerge en XP para comparar la fecha de modificación y el tamaño de los archivos en el servidor versus los archivos localmente. Esto iteraba en todo el árbol de directorios de 33,000 archivos en cada carpeta. Tiempo total, 7 segundos java: más de 5 minutos.

Entonces, la statement y pregunta original del OP es verdadera y válida. Es menos notable cuando se trata de un sistema de archivos local. Hacer una comparación local de la carpeta con 33,000 elementos toma 3 segundos en WinMerge, y toma 32 segundos localmente en Java. Así que de nuevo, Java versus nativo es una ralentización de 10 veces en estas pruebas rudimentarias.

Java 1.6.0_22 (última), Gigabit LAN y conexiones de red, el ping es inferior a 1 ms (ambos en el mismo conmutador)

Java es lento.

Desde el punto de referencia de GHad, hay algunas personas que han mencionado:

1> Como BalusC mencionó: stream.available () fluye en este caso.

Porque available () devuelve una estimación de la cantidad de bytes que se pueden leer (o omitir) de esta secuencia de entrada sin locking mediante la siguiente invocación de un método para esta secuencia de entrada.

Entonces primero para eliminar la URL este enfoque.

2> Como StuartH mencionó: el orden en que se ejecuta la prueba también hace que la diferencia de caché, así que elimine esto ejecute la prueba por separado.


Ahora comienza la prueba:

Cuando el CANAL uno se ejecuta solo:

 CHANNEL sum: 59691, per Iteration: 238.764 

Cuando LONGITUD corre solo:

 LENGTH sum: 48268, per Iteration: 193.072 

Parece que LENGTH es el ganador aquí:

 @Override public long getResult() throws Exception { File me = new File(FileSizeBench.class.getResource( "FileSizeBench.class").getFile()); return me.length(); }