Mientras busco en Google, veo que el uso java.io.File#length()
puede ser lento.
FileChannel
tiene un size()
método que también está disponible.
¿Hay alguna manera eficiente en Java para obtener el tamaño del archivo?
Mientras busco en Google, veo que el uso java.io.File#length()
puede ser lento.
FileChannel
tiene un size()
método que también está disponible.
¿Hay alguna manera eficiente en Java para obtener el tamaño del archivo?
Respuestas:
Bueno, intenté medirlo con el siguiente código:
Para carreras = 1 e iteraciones = 1, el método de URL es más rápido la mayoría de las veces seguido de canal. Ejecuto esto con una pausa fresca unas 10 veces. Entonces, para acceder una vez, usar la URL es la forma más rápida en la 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 carreras = 5 e iteraciones = 50, la imagen se dibuja de manera 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 algo de 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<FileSizeBench, Long> durations = new EnumMap<FileSizeBench, Long>(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<FileSizeBench, Long> 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;
}
}
stream.available()
no devuelve la longitud del archivo. Devuelve la cantidad de bytes que están disponibles para leer sin bloquear otras secuencias. No es necesariamente la misma cantidad de bytes que la longitud del archivo. Para obtener la longitud real de una secuencia, realmente necesita leerla (y contar los bytes leídos mientras tanto).
El punto de referencia proporcionado por GHad mide muchas otras cosas (como la reflexión, la creación de instancias de objetos, etc.) además de obtener la longitud. Si intentamos deshacernos de estas cosas, en una llamada obtengo los siguientes tiempos en microsegundos:
suma de archivos ___ 19.0, por iteración ___ 19.0 suma de raf ___ 16.0, por iteración ___ 16.0 suma de canales__273.0, por iteración__273.0
Por 100 ejecuciones y 10000 iteraciones obtengo:
suma de archivos__1767629.0, por iteración__1.7676290000000001 suma raf ___ 881284.0, por iteración__0.8812840000000001 suma de canales ___ 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<String, Double> times = new HashMap<String, Double>();
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<String, Double> 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 son defectuosos ya que acceden al mismo archivo para cada método probado. Entonces, las patadas de almacenamiento en caché de disco en las que se benefician 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.
Mirando el resultado, creo que File.length () es realmente el ganador.
El orden de prueba es el orden de salida. Incluso puede ver que el tiempo empleado en mi máquina varió entre ejecuciones, pero File.Length () cuando no fue el primero, e incurrió en el primer acceso al 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; los tiempos para un archivo de 10 bytes son idénticos a 100,000 bytes )
LONGITUD suma: 33, por iteración: 33.0
CANAL suma: 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 utilizar el archivo para otros fines, como leer / escribir, RAF parece ser una mejor opción. Simplemente no olvide cerrar la conexión de 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<String, Long> times = new HashMap<String, Long>();
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<String, Long> 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 del archivo y la fecha de modificación de 90,000 archivos en un recurso compartido de red. Usando Java, y siendo lo más minimalista posible, tomaría mucho tiempo. (Necesitaba obtener la URL del archivo y la ruta del objeto también. Por lo tanto, varió un poco, pero más de una hora). Luego utilicé un ejecutable Win32 nativo, e hice la misma tarea, simplemente volqué el archivo ruta, modificado y tamaño a la consola, y ejecutó eso desde Java. La velocidad fue asombrosa. El proceso nativo y el manejo de mi cadena para leer los datos podrían procesar más de 1000 elementos por segundo.
Entonces, aunque las personas clasificaron el comentario anterior, esta es una solución válida y resolvió mi problema. En mi caso, sabía de antemano las carpetas que necesitaba los tamaños, y podría pasar eso en la línea de comandos a mi aplicación win32. Pasé de 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 de los archivos de red tan rápido como el SO podía hacerlo.
El manejo de archivos Java en Windows es terrible. Sin embargo, el acceso al disco local para los archivos está bien. Fueron solo los recursos compartidos de red los que causaron el terrible rendimiento. Windows también podría obtener información sobre el recurso compartido de red y calcular el tamaño total en menos de un minuto.
--Ben
Si desea el tamaño de archivo de varios archivos en un directorio, use Files.walkFileTree
. Puede obtener el tamaño del BasicFileAttributes
que recibirá.
Esto es mucho más rápido que invocar .length()
el resultado de File.listFiles()
o usar Files.size()
el resultado de Files.newDirectoryStream()
. En mis casos de prueba fue aproximadamente 100 veces más rápido.
Files.walkFileTree
está disponible en Android 26+.
En realidad, creo que el "ls" puede ser más rápido. 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 ls recursivo para Windows. (DIR / S de cmd.exe puede confundirse y generar errores en bucles infinitos)
En XP, al acceder a un servidor en la LAN, me toma 5 segundos en Windows obtener el recuento de los archivos en una carpeta (33,000) y el tamaño total.
Cuando itero 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 descubrí es que esas 3 llamadas me toman el 99% de mi tiempo. Las 3 llamadas que realmente necesito hacer ...
La diferencia para 1000 archivos es de 15 ms local frente a 1800 ms en el servidor. El escaneo de la ruta del servidor en Java es ridículamente lento. Si el sistema operativo nativo puede ser rápido al escanear 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 sobre el árbol de directorios completo de 33,000 archivos en cada carpeta. Tiempo total, 7 segundos. Java: más de 5 minutos.
Por lo tanto, la declaración original y la pregunta del OP son verdaderas y válidas. Es menos notable cuando se trata de un sistema de archivos local. Hacer una comparación local de la carpeta con 33,000 elementos lleva 3 segundos en WinMerge y 32 segundos localmente en Java. De nuevo, java versus native es una desaceleración 10x en estas pruebas rudimentarias.
Java 1.6.0_22 (más reciente), LAN Gigabit 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 algunos problemas que la gente ha mencionado:
1> Como BalusC mencionó: stream.available () fluye en este caso.
Porque disponible () devuelve una estimación del número de bytes que se pueden leer (u omitir) de esta secuencia de entrada sin bloquear mediante la próxima invocación de un método para esta secuencia de entrada.
Así que primero para eliminar la URL de este enfoque.
2> Como mencionó StuartH: el orden en que se ejecuta la prueba también hace la diferencia de caché, así que sáquelo ejecutando la prueba por separado.
Ahora comience la prueba:
Cuando el CANAL uno corre solo:
CHANNEL sum: 59691, per Iteration: 238.764
Cuando LONGITUD se corre solo:
LENGTH sum: 48268, per Iteration: 193.072
Parece que LENGTH one es el ganador aquí:
@Override
public long getResult() throws Exception {
File me = new File(FileSizeBench.class.getResource(
"FileSizeBench.class").getFile());
return me.length();
}