Domanda

Mentre googling, vedo che usando java.io.File # length () può essere lento. FileChannel ha un metodo size () disponibile anche.

Esiste un modo efficiente in Java per ottenere le dimensioni del file?

È stato utile?

Soluzione

Beh, ho provato a misurarlo con il codice seguente:

Per esecuzioni = 1 e iterazioni = 1 il metodo URL è più veloce la maggior parte delle volte seguito dal canale. Lo eseguo con qualche pausa fresca circa 10 volte. Quindi per un accesso una volta, usare l'URL è il modo più veloce che mi viene in mente:

LENGTH sum: 10626, per Iteration: 10626.0

CHANNEL sum: 5535, per Iteration: 5535.0

URL sum: 660, per Iteration: 660.0

Per esecuzioni = 5 e iterazioni = 50 l'immagine viene disegnata in modo diverso.

LENGTH sum: 39496, per Iteration: 157.984

CHANNEL sum: 74261, per Iteration: 297.044

URL sum: 95534, per Iteration: 382.136

Il file deve memorizzare nella cache le chiamate al filesystem, mentre i canali e l'URL hanno un overhead.

Codice:

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

}

Altri suggerimenti

Il benchmark fornito da GHad misura molte altre cose (come la riflessione, l'istanza di oggetti, ecc.) oltre a ottenere la lunghezza. Se proviamo a sbarazzarci di queste cose, per una chiamata ottengo i seguenti tempi in microsecondi:

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

Per 100 esecuzioni e 10000 iterazioni ottengo:

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

Ho eseguito il seguente codice modificato fornendo come argomento il nome di un file da 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));
    }
  }
}

Tutti i casi di test in questo post sono imperfetti in quanto accedono allo stesso file per ogni metodo testato. Quindi il caching del disco dà dei calci ai benefici dei test 2 e 3. Per dimostrare il mio punto ho preso il caso di prova fornito da GHAD e ho cambiato l'ordine di enumerazione e di seguito sono riportati i risultati.

Guardando il risultato penso che File.length () sia davvero il vincitore.

L'ordine di prova è l'ordine di uscita. Puoi anche vedere il tempo impiegato sulla mia macchina varia tra le esecuzioni, ma File.Length () quando non è il primo e il primo accesso al disco ha vinto.

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

Quando modifico il codice per utilizzare un file a cui accede tramite un percorso assoluto anziché una risorsa, ottengo un risultato diverso (per 1 esecuzione, 1 iterazione e un file di 100.000 byte - i tempi per un file di 10 byte sono identici fino a 100.000 byte)

LUNGHEZZA somma: 33, per Iterazione: 33.0

Somma CANALE: 3626, per Iterazione: 3626,0

Somma URL: 294, per Iterazione: 294.0

In risposta al benchmark di rgrig, il tempo impiegato per aprire / chiudere FileChannel & amp; Anche le istanze di RandomAccessFile devono essere prese in considerazione, poiché queste classi apriranno un flusso per la lettura del file.

Dopo aver modificato il benchmark, ho ottenuto questi risultati per 1 iterazione su un file da 85 MB:

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

Per 10000 iterazioni sullo stesso file:

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

Se tutto ciò di cui hai bisogno è la dimensione del file, file.length () è il modo più veloce per farlo. Se hai intenzione di utilizzare il file per altri scopi come la lettura / scrittura, RAF sembra essere una scommessa migliore. Non dimenticare di chiudere la connessione al file :-)

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

Ho riscontrato questo stesso problema. Avevo bisogno di ottenere la dimensione del file e la data modificata di 90.000 file su una condivisione di rete. Usando Java, ed essendo il più minimalista possibile, ci vorrebbe molto tempo. (Avevo bisogno di ottenere l'URL dal file e anche il percorso dell'oggetto. Quindi è variato un po ', ma più di un'ora.) Quindi ho usato un eseguibile Win32 nativo e ho fatto la stessa attività, scaricando semplicemente il file percorso, modificato e dimensioni alla console, ed eseguito da Java. La velocità è stata sorprendente. Il processo nativo e la mia gestione delle stringhe per leggere i dati potrebbero elaborare oltre 1000 elementi al secondo.

Quindi, anche se le persone hanno valutato il commento sopra, questa è una soluzione valida e ha risolto il mio problema. Nel mio caso conoscevo le cartelle di cui avevo bisogno in anticipo delle dimensioni e potevo passarle dalla riga di comando alla mia app win32. Sono passato da ore a elaborare una directory in minuti.

Il problema sembrava essere specifico di Windows. OS X non presentava lo stesso problema e poteva accedere alle informazioni sui file di rete il più rapidamente possibile.

La gestione dei file Java su Windows è terribile. L'accesso al disco locale per i file va bene però. Sono state solo le condivisioni di rete a causare le terribili prestazioni. Windows potrebbe ottenere informazioni sulla condivisione di rete e calcolare anche le dimensioni totali in meno di un minuto.

- Ben

Se si desidera la dimensione del file di più file in una directory, utilizzare Files.walkFileTree . Puoi ottenere la dimensione dal BasicFileAttributes che riceverai.

Questo è molto più veloce della chiamata .length () sul risultato di File.listFiles () o usando Files.size () sul risultato di Files.newDirectoryStream () . Nei miei casi di test era circa 100 volte più veloce.

In realtà, penso che "ls" potrebbe essere più veloce. Ci sono sicuramente alcuni problemi in Java relativi all'ottenimento delle informazioni sui file. Sfortunatamente non esiste un metodo sicuro equivalente per ls ricorsivo per Windows. (DIR / S di cmd.exe può essere confuso e generare errori in cicli infiniti)

Su XP, accedendo a un server sulla LAN, mi ci vogliono 5 secondi in Windows per ottenere il conteggio dei file in una cartella (33.000) e la dimensione totale.

Quando eseguo un'iterazione ricorsiva in Java, mi ci vogliono più di 5 minuti. Ho iniziato a misurare il tempo necessario per eseguire file.length (), file.lastModified () e file.toURI () e quello che ho scoperto è che il 99% del mio tempo è impiegato da quelle 3 chiamate. Le 3 chiamate che devo effettivamente fare ...

La differenza per 1000 file è 15ms locali contro 1800ms sul server. La scansione del percorso del server in Java è ridicolmente lenta. Se il sistema operativo nativo può essere veloce nella scansione della stessa cartella, perché non è possibile Java?

Come test più completo, ho usato WineMerge su XP per confrontare la data modificata e la dimensione dei file sul server rispetto ai file locali. Questo stava ripetendo l'intero albero di directory di 33.000 file in ogni cartella. Tempo totale, 7 secondi. java: oltre 5 minuti.

Quindi l'affermazione e la domanda originali dell'OP sono vere e valide. È meno evidente quando si ha a che fare con un file system locale. Fare un confronto locale della cartella con 33.000 elementi richiede 3 secondi in WinMerge e impiega 32 secondi localmente in Java. Quindi, di nuovo, java contro native è un rallentamento di 10 volte in questi test rudimentali.

Java 1.6.0_22 (più recente), Gigabit LAN e connessioni di rete, il ping è inferiore a 1ms (entrambi nello stesso switch)

Java è lento.

Dal benchmark di GHad, ci sono alcuni problemi che la gente ha menzionato:

1 > Come BalusC menzionato: stream.available () scorre in questo caso.

Perché disponibile () restituisce una stima del numero di byte che possono essere letti (o ignorati) da questo flusso di input senza bloccarsi con la successiva chiamata di un metodo per questo flusso di input.

/ p>

Quindi 1 ° per rimuovere l'URL questo approccio.

2 > Come accennato da StuartH, l'ordine in cui viene eseguito il test fa anche la differenza nella cache, quindi eliminalo eseguendo il test separatamente.


Ora inizia il test:

Quando CHANNEL ne viene eseguito uno da solo:

CHANNEL sum: 59691, per Iteration: 238.764

Quando LENGTH viene eseguito da solo:

LENGTH sum: 48268, per Iteration: 193.072

Quindi sembra che LUNGHEZZA sia il vincitore qui:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top