java ottiene le dimensioni del file in modo efficiente


166

Mentre googling, vedo che l'utilizzo java.io.File#length()può essere lento. FileChannelha anche un size()metodo disponibile.

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


7
puoi fornire i link dicendo che File.length () "può essere lento"?
matt b

1
scusate, ecco il link javaperformancetuning.com/tips/rawtips.shtml cerca "Le informazioni sui file come File.length () richiedono una chiamata di sistema e possono essere lente". è davvero un'affermazione confusa, sembra quasi ipotizzato che sarebbe una chiamata di sistema.
joshjdevl,

25
Ottenere la lunghezza del file richiederà una chiamata di sistema, indipendentemente da come lo si fa. Potrebbe essere lento se si trova su una rete o su un altro filesystem molto lento. Non c'è modo più veloce per ottenerlo di File.length (), e la definizione di "lento" qui significa semplicemente non chiamarlo inutilmente.
jsight,

Penso che sia quello che GHad stava cercando di testare di seguito. I miei risultati sono (su Ubuntu 8.04): solo un URL di accesso è più veloce. 5 run, 50 iterations CHANNEL è ancora il più veloce tra i confusi? :) per i miei scopi però, farò solo un accesso. anche se è strano? che abbiamo ottenuto risultati diversi
joshjdevl,

1
Questa operazione può essere molto lenta se le informazioni si trovano sul disco anziché nella cache. (come 1000 volte più lento), tuttavia, c'è poco da fare a riguardo oltre a garantire che le informazioni di cui hai bisogno siano sempre nella cache (come pre-caricarle e avere abbastanza memoria in modo che rimanga in memoria)
Peter Lawrey

Risposte:


102

Bene, ho provato a misurarlo con il codice seguente:

Per esecuzioni = 1 e iterazioni = 1 il metodo URL è il più veloce la maggior parte delle volte seguito dal canale. Lo eseguo con qualche pausa fresca circa 10 volte. Quindi, per l'accesso singolo, l'utilizzo dell'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 presentano un sovraccarico.

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

}

1
Sembra che il modo in cui l'URL sia il migliore per l'accesso singolo sia su XP che su Linux. Greetz GHad
GHad,

73
stream.available()non restituisce la lunghezza del file. Restituisce la quantità di byte disponibili per la lettura senza bloccare altri flussi. Non è necessariamente la stessa quantità di byte della lunghezza del file. Per ottenere la lunghezza reale da uno stream, devi davvero leggerlo (e nel frattempo contare i byte letti).
BalusC,

11
Questo benchmark è o piuttosto la sua interpretazione non è corretta. Nel conteggio di iterazioni basse, i test successivi sfruttano la memorizzazione nella cache dei file del sistema operativo. Nei test di iterazioni superiori la classifica è corretta ma non perché File.length () sta memorizzando nella cache qualcosa, ma semplicemente perché le altre 2 opzioni sono basate sullo stesso metodo ma fanno un lavoro extra che le rallenta.
x4u,

2
@Paolo, la memorizzazione nella cache e l'ottimizzazione dell'accesso al file system è una delle principali responsabilità di un sistema operativo. faqs.org/docs/linux_admin/buffer-cache.html Per ottenere buoni risultati di benchmarking, è necessario svuotare la cache prima di ogni esecuzione.
z0r

3
Oltre a ciò che dice javadoc per InputStream.available (), il fatto che il metodo available () restituisca un int dovrebbe essere una bandiera rossa contro l'approccio URL. Provalo con un file da 3 GB e sarà ovvio che non è un modo valido per determinare la lunghezza del file.
Scrubbie,

32

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

   somma file ___ 19.0, per Iterazione ___ 19.0
    somma raf ___ 16.0, per Iterazione ___ 16.0
somma del canale__273.0, per Iteration__273.0

Per 100 esecuzioni e 10000 iterazioni ottengo:

   somma file__1767629.0, per Iterazione__1.7676290000000001
    somma raf ___ 881284.0, per Iterazione__0.8812840000000001
somma del canale ___ 414286.0, per Iterazione__0.414286

Ho eseguito il seguente codice modificato dando 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));
    }
  }
}

3
in realtà, mentre hai ragione nel dire che misura altri aspetti, dovrei essere più chiaro nella mia domanda. Sto cercando di ottenere la dimensione del file di più file e voglio il modo più rapido possibile. quindi ho davvero bisogno di prendere in considerazione la creazione e l'overhead degli oggetti, dato che questo è uno scenario reale
joshjdevl,

3
Circa il 90% del tempo viene impiegato in quella cosa getResource. Dubito che sia necessario utilizzare la riflessione per ottenere il nome di un file che contiene alcuni bytecode Java.

20

Tutti i casi di test in questo post sono imperfetti poiché accedono allo stesso file per ciascun 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 i risultati.

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

9

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 da 100.000 byte - i tempi per un file da 10 byte sono identici a 100.000 byte )

LUNGHEZZA somma: 33, per Iterazione: 33.0

Somma CHANNEL: 3626, per Iterazione: 3626.0

Somma URL: 294, per Iterazione: 294.0


9

In risposta al benchmark di rgrig, è necessario prendere in considerazione anche il tempo impiegato per aprire / chiudere le istanze FileChannel & RandomAccessFile, 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ò che serve è 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. Basta 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";
        } 
    }
}

8

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 in fondo hanno classificato il commento sopra, questa è una soluzione valida e ha risolto il mio problema. Nel mio caso conoscevo le cartelle di cui avevo bisogno prima delle dimensioni, e potevo passarle dalla riga di comando alla mia app win32. Sono passato da ore a elaborare una directory in minuti.

Anche 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


3

Se si desidera la dimensione del file di più file in una directory, utilizzare Files.walkFileTree. Puoi ottenere la dimensione da quella BasicFileAttributesche riceverai.

Questo è molto più veloce quindi invocare .length()il risultato File.listFiles()o utilizzare Files.size()il risultato di Files.newDirectoryStream(). Nei miei casi di test era circa 100 volte più veloce.


Cordiali saluti, Files.walkFileTreeè disponibile su Android 26+.
Joshua Pinter,

2

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, sono necessari 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 locale rispetto a 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. Il confronto locale della cartella con 33.000 elementi richiede 3 secondi in WinMerge e 32 secondi in locale in Java. Quindi, di nuovo, java contro nativo è un rallentamento 10x 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.


2
Anche questo sembra essere specifico del sistema operativo. Per eseguire la stessa app java seguendo la stessa cartella da OS X usando samba ci sono voluti 26 secondi per elencare tutti gli 33.000 articoli, dimensioni e date. Quindi la rete Java è solo lenta su Windows allora? (OS X era anche java 1.6.0_22.)
Ben Spink il

2

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

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

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

Quindi 1 ° per rimuovere l'URL questo approccio.

2> Come accennato a 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 uno funziona 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();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.