Pregunta

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

¿Existe una forma eficaz en Java de obtener el tamaño del archivo?

¿Fue útil?

Solución

Bueno, intenté medirlo con el siguiente código:

Para ejecuciones = 1 e iteraciones = 1, el método de URL es el más rápido la mayoría de las veces, seguido del canal.Ejecuto esto con una pausa nueva unas 10 veces.Entonces, para un acceso único, usar la URL es la forma más rápida que se me ocurre:

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

}

Otros consejos

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, para una llamada obtengo los siguientes tiempos en microsegundos:

   file sum___19.0, per Iteration___19.0
    raf sum___16.0, per Iteration___16.0
channel sum__273.0, per Iteration__273.0

Para 100 ejecuciones y 10000 iteraciones obtengo:

   file sum__1767629.0, per Iteration__1.7676290000000001
    raf sum___881284.0, per Iteration__0.8812840000000001
channel sum___414286.0, per Iteration__0.414286

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

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 tienen fallas ya que acceden al mismo archivo para cada método probado.Entonces se activa el almacenamiento en caché del disco, del que se benefician las pruebas 2 y 3.Para demostrar 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 la prueba es el orden de salida.Incluso puede ver que el tiempo necesario en mi máquina varió entre las ejecuciones, pero File.Length() cuando no fue el primero y ganó el primer acceso al disco.

---
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 mediante 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

Suma de CANALES:3626, por iteración:3626.0

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

En respuesta al punto de referencia de rgrig, también se debe tener en cuenta el tiempo necesario para abrir/cerrar las instancias FileChannel y RandomAccessFile, 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 iteración 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, entonces RAF parece ser una mejor opción.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<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, llevaría muchísimo tiempo.(Necesitaba obtener la URL del archivo y también la ruta del objeto.Así que varía un poco, pero más de una hora). Luego utilicé un ejecutable Win32 nativo e hice la misma tarea, simplemente volcando la ruta del archivo, la modificación y el tamaño a la consola, y lo ejecuté desde Java.La velocidad fue asombrosa.El proceso nativo y mi manejo de cadenas para leer los datos podrían procesar más de 1000 elementos por segundo.

Entonces, aunque la gente menospreció el comentario anterior, esta es una solución válida y resolvió mi problema.En mi caso, sabía de antemano el tamaño de las carpetas que necesitaba y podía pasarlas en la línea de comando 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 lo hacía el sistema operativo.

El manejo de archivos Java en Windows es terrible.Sin embargo, el acceso al disco local para archivos está bien.Fueron simplemente 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.Puedes obtener el tamaño en el BasicFileAttributes que recibirás.

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

De hecho, creo que "ls" puede ser más rápido.Definitivamente hay algunos problemas en Java relacionados con la obtención de información del archivo.Lamentablemente, no existe un método seguro equivalente de ls 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 recuento de archivos en una carpeta (33.000) y el tamaño total.

Cuando repito recursivamente esto en Java, me lleva más de 5 minutos.Comencé a medir el tiempo que lleva hacer file.length(), file.lastModified() y file.toURI() y lo que descubrí 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 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 escanear rápidamente esa misma carpeta, ¿por qué Java no puede hacerlo?

Como 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 con los archivos localmente.Esto recorrió todo el árbol de directorios de 33.000 archivos en cada carpeta.Tiempo total, 7 segundos.Java:más de 5 minutos.

Entonces, la declaración y la pregunta originales del OP son verdaderas y válidas.Es menos perceptible 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.Nuevamente, java versus nativo es una desaceleración 10 veces mayor en estas pruebas rudimentarias.

Java 1.6.0_22 (más reciente), 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 algunos problemas que la gente ha mencionado:

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

Porque disponible() devuelve un estimar del número de bytes que se pueden leer (u omitir) de este flujo de entrada sin bloquearse en la siguiente invocación de un método para este flujo de entrada.

Así que primero elimine la URL con este enfoque.

2> Como mencionó StuartH, el orden en que se ejecuta la prueba también marca la diferencia en el caché, así que elimínelo ejecutando la prueba por separado.


Ahora comienza la prueba:

Cuando el CANAL uno se ejecuta solo:

CHANNEL sum: 59691, per Iteration: 238.764

Cuando LENGTH se ejecuta solo:

LENGTH sum: 48268, per Iteration: 193.072

Parece que el de LONGITUD es el ganador aquí:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top