Beim googeln sehe ich, dass die Verwendung java.io.File#length()
langsam sein kann.
FileChannel
hat eine size()
Methode, die ebenfalls verfügbar ist.
Gibt es in Java eine effiziente Möglichkeit, die Dateigröße zu ermitteln?
Beim googeln sehe ich, dass die Verwendung java.io.File#length()
langsam sein kann.
FileChannel
hat eine size()
Methode, die ebenfalls verfügbar ist.
Gibt es in Java eine effiziente Möglichkeit, die Dateigröße zu ermitteln?
Antworten:
Nun, ich habe versucht, es mit dem folgenden Code zu messen:
Bei Läufen = 1 und Iterationen = 1 ist die URL-Methode meistens am schnellsten, gefolgt vom Kanal. Ich führe dies mit einer Pause etwa 10 Mal frisch. Für einen einmaligen Zugriff ist die Verwendung der URL der schnellste Weg, den ich mir vorstellen kann:
LENGTH sum: 10626, per Iteration: 10626.0
CHANNEL sum: 5535, per Iteration: 5535.0
URL sum: 660, per Iteration: 660.0
Für Läufe = 5 und Iterationen = 50 zeichnet das Bild anders.
LENGTH sum: 39496, per Iteration: 157.984
CHANNEL sum: 74261, per Iteration: 297.044
URL sum: 95534, per Iteration: 382.136
Die Datei muss die Aufrufe an das Dateisystem zwischenspeichern, während Kanäle und URL einen gewissen Overhead haben.
Code:
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()
gibt die Dateilänge nicht zurück. Es gibt die Anzahl der Bytes zurück, die zum Lesen verfügbar sind, ohne andere Streams zu blockieren. Es ist nicht unbedingt die gleiche Anzahl von Bytes wie die Dateilänge. Um die tatsächliche Länge eines Streams zu erhalten, müssen Sie ihn wirklich lesen (und in der Zwischenzeit die gelesenen Bytes zählen).
Der von GHad vorgegebene Benchmark misst neben der Länge viele andere Dinge (wie Reflexion, Instanziierung von Objekten usw.). Wenn wir versuchen, diese Dinge loszuwerden, erhalte ich für einen Anruf die folgenden Zeiten in Mikrosekunden:
Dateisumme ___ 19,0, pro Iteration ___ 19,0 raf sum ___ 16.0, per Iteration ___ 16.0 Kanalsumme__273.0, pro Iteration__273.0
Für 100 Läufe und 10000 Iterationen bekomme ich:
Dateisumme__1767629.0, pro Iteration__1.7676290000000001 raf sum ___ 881284.0, per Iteration__0.8812840000000001 Kanalsumme ___ 414286.0, pro Iteration__0.414286
Ich habe den folgenden modifizierten Code ausgeführt und als Argument den Namen einer 100-MB-Datei angegeben.
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));
}
}
}
Alle Testfälle in diesem Beitrag sind fehlerhaft, da sie für jede getestete Methode auf dieselbe Datei zugreifen. Das Festplatten-Caching beginnt also, von dem die Tests 2 und 3 profitieren. Um meinen Standpunkt zu beweisen, habe ich einen von GHAD bereitgestellten Testfall genommen und die Reihenfolge der Aufzählung geändert. Nachfolgend sind die Ergebnisse aufgeführt.
Mit Blick auf das Ergebnis denke ich, dass File.length () wirklich der Gewinner ist.
Die Reihenfolge des Tests ist die Reihenfolge der Ausgabe. Sie können sogar sehen, dass die auf meinem Computer benötigte Zeit zwischen den Ausführungen variiert, aber File.Length (), wenn nicht zuerst, und der erste gewonnene Festplattenzugriff.
---
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
Wenn ich Ihren Code so ändere, dass anstelle einer Ressource eine Datei verwendet wird, auf die über einen absoluten Pfad zugegriffen wird, erhalte ich ein anderes Ergebnis (für 1 Lauf, 1 Iteration und eine 100.000-Byte-Datei - die Zeiten für eine 10-Byte-Datei sind identisch mit 100.000 Byte )
LÄNGE Summe: 33, pro Iteration: 33,0
CHANNEL-Summe: 3626, pro Iteration: 3626,0
URL-Summe: 294, pro Iteration: 294,0
In Reaktion auf den Benchmark von rgrig muss auch die Zeit berücksichtigt werden, die zum Öffnen / Schließen der FileChannel- und RandomAccessFile-Instanzen benötigt wird, da diese Klassen einen Stream zum Lesen der Datei öffnen.
Nachdem ich den Benchmark geändert hatte, erhielt ich diese Ergebnisse für 1 Iteration in einer 85-MB-Datei:
file totalTime: 48000 (48 us)
raf totalTime: 261000 (261 us)
channel totalTime: 7020000 (7 ms)
Für 10000 Iterationen in derselben Datei:
file totalTime: 80074000 (80 ms)
raf totalTime: 295417000 (295 ms)
channel totalTime: 368239000 (368 ms)
Wenn Sie nur die Dateigröße benötigen, ist file.length () der schnellste Weg, dies zu tun. Wenn Sie die Datei für andere Zwecke wie Lesen / Schreiben verwenden möchten, ist RAF anscheinend die bessere Wahl. Vergiss nur nicht, die Dateiverbindung zu schließen :-)
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";
}
}
}
Ich bin auf dasselbe Problem gestoßen. Ich musste die Dateigröße und das Änderungsdatum von 90.000 Dateien auf einer Netzwerkfreigabe ermitteln. Wenn Sie Java verwenden und so minimalistisch wie möglich sind, würde dies sehr lange dauern. (Ich musste die URL aus der Datei und auch den Pfad des Objekts abrufen. Sie variierte also etwas, aber mehr als eine Stunde.) Dann verwendete ich eine native ausführbare Win32-Datei und erledigte dieselbe Aufgabe, indem ich nur die Datei ablegte Pfad, geändert und Größe zur Konsole und ausgeführt von Java. Die Geschwindigkeit war unglaublich. Der native Prozess und meine Zeichenfolgenbehandlung zum Lesen der Daten können über 1000 Elemente pro Sekunde verarbeiten.
Obwohl die Leute den obigen Kommentar herabgestuft haben, ist dies eine gültige Lösung und hat mein Problem gelöst. In meinem Fall kannte ich die Ordner, deren Größe ich benötigte, im Voraus und konnte diese in der Befehlszeile an meine win32-App übergeben. Ich ging von Stunden, um ein Verzeichnis zu Minuten zu verarbeiten.
Das Problem schien auch Windows-spezifisch zu sein. OS X hatte nicht das gleiche Problem und konnte so schnell wie das Betriebssystem auf Netzwerkdatei-Informationen zugreifen.
Die Handhabung von Java-Dateien unter Windows ist schrecklich. Der lokale Festplattenzugriff für Dateien ist jedoch in Ordnung. Es waren nur Netzwerkfreigaben, die die schreckliche Leistung verursachten. Windows könnte Informationen über die Netzwerkfreigabe erhalten und die Gesamtgröße in weniger als einer Minute berechnen.
- Ben
Wenn Sie die Dateigröße mehrerer Dateien in einem Verzeichnis möchten, verwenden Sie Files.walkFileTree
. Sie können die Größe von der erhalten BasicFileAttributes
, die Sie erhalten.
Dies ist viel schneller, als .length()
das Ergebnis von aufzurufen File.listFiles()
oder Files.size()
das Ergebnis von zu verwenden Files.newDirectoryStream()
. In meinen Testfällen war es ungefähr 100 mal schneller.
Files.walkFileTree
ist auf Android 26+ verfügbar.
Eigentlich denke ich, dass das "ls" schneller sein kann. In Java gibt es definitiv einige Probleme beim Abrufen von Dateiinformationen. Leider gibt es für Windows keine gleichwertige sichere Methode für rekursives ls. (cmd.exes DIR / S kann verwirrt werden und Fehler in Endlosschleifen erzeugen.)
Unter XP, wenn ich auf einen Server im LAN zugreife, brauche ich unter Windows 5 Sekunden, um die Anzahl der Dateien in einem Ordner (33.000) und die Gesamtgröße zu ermitteln.
Wenn ich dies in Java rekursiv durchlaufe, dauert es über 5 Minuten. Ich habe angefangen, die Zeit zu messen, die für file.length (), file.lastModified () und file.toURI () benötigt wird. Dabei habe ich festgestellt, dass 99% meiner Zeit für diese drei Aufrufe benötigt werden. Die 3 Anrufe, die ich eigentlich machen muss ...
Der Unterschied für 1000 Dateien beträgt 15 ms lokal gegenüber 1800 ms auf dem Server. Das Scannen von Serverpfaden in Java ist lächerlich langsam. Wenn das native Betriebssystem denselben Ordner schnell scannen kann, warum kann Java dann nicht?
Als vollständigeren Test habe ich WineMerge unter XP verwendet, um das Änderungsdatum und die Größe der Dateien auf dem Server mit den Dateien vor Ort zu vergleichen. Dies wurde über den gesamten Verzeichnisbaum von 33.000 Dateien in jedem Ordner wiederholt. Gesamtzeit 7 Sekunden. Java: über 5 Minuten.
Die ursprüngliche Aussage und Frage des OP ist also wahr und gültig. Es ist weniger auffällig, wenn es sich um ein lokales Dateisystem handelt. Das lokale Vergleichen des Ordners mit 33.000 Elementen dauert in WinMerge 3 Sekunden und in Java 32 Sekunden lokal. Java versus Native ist also eine 10-fache Verlangsamung in diesen rudimentären Tests.
Java 1.6.0_22 (aktuell), Gigabit LAN und Netzwerkverbindungen, Ping ist weniger als 1 ms (beide im selben Switch)
Java ist langsam.
Aus dem GHad-Benchmark sind einige Punkte hervorgegangen:
1> Wie von BalusC erwähnt: stream.available () wird in diesem Fall übertragen.
Weil available () eine Schätzung der Anzahl von Bytes zurückgibt , die aus diesem Eingabestream gelesen (oder übersprungen) werden können, ohne beim nächsten Aufruf einer Methode für diesen Eingabestream blockiert zu werden.
Also zuerst die URL dieses Ansatzes entfernen.
2> Wie StuartH erwähnt hat - die Reihenfolge, in der der Testlauf ausgeführt wird, macht auch den Cache-Unterschied aus. Nehmen Sie dies heraus, indem Sie den Test separat ausführen.
Starten Sie nun den Test:
Beim CHANNEL läuft man alleine:
CHANNEL sum: 59691, per Iteration: 238.764
Bei LÄNGE läuft man alleine:
LENGTH sum: 48268, per Iteration: 193.072
Sieht also so aus, als wäre die LÄNGE hier der Gewinner:
@Override
public long getResult() throws Exception {
File me = new File(FileSizeBench.class.getResource(
"FileSizeBench.class").getFile());
return me.length();
}