Mentre googling, vedo che l'utilizzo java.io.File#length()
può essere lento.
FileChannel
ha anche un size()
metodo disponibile.
Esiste un modo efficiente in Java per ottenere le dimensioni del file?
Mentre googling, vedo che l'utilizzo java.io.File#length()
può essere lento.
FileChannel
ha anche un size()
metodo disponibile.
Esiste un modo efficiente in Java per ottenere le dimensioni del file?
Risposte:
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;
}
}
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).
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));
}
}
}
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
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
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";
}
}
}
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
Se si desidera la dimensione del file di più file in una directory, utilizzare Files.walkFileTree
. Puoi ottenere la dimensione da quella BasicFileAttributes
che 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.
Files.walkFileTree
è disponibile su Android 26+.
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.
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();
}