it-swarm.com.de

java Dateigröße effizient ermitteln

Beim Googeln sehe ich, dass die Verwendung von 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?

160
joshjdevl

Nun, ich habe versucht, es mit dem folgenden Code zu messen:

Bei Läufen = 1 und Iterationen = 1 ist die URL-Methode am schnellsten, gefolgt von Channel. Ich führe das mit einer Pause ungefähr 10 Mal frisch aus. 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 wird ein anderes Bild gezeichnet.

LENGTH sum: 39496, per Iteration: 157.984

CHANNEL sum: 74261, per Iteration: 297.044

URL sum: 95534, per Iteration: 382.136

File muss die Aufrufe an das Dateisystem zwischenspeichern, während Channels 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;
    }

}
99
GHad

Der von GHad gegebene Benchmark misst viele andere Dinge (wie Reflektion, Instanziierung von Objekten usw.) neben der Ermittlung der Länge. 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-Summe ___ 16.0 pro 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-Summe ___ 881284.0, pro 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));
    }
  }
}
32
anon

Alle Testfälle in diesem Beitrag sind fehlerhaft, da sie für jede getestete Methode auf dieselbe Datei zugreifen. Also Disk-Caching-Kicks, von denen Test 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.

Nach dem Ergebnis denke ich, dass File.length () der Gewinner ist.

Testreihenfolge ist die Reihenfolge der Ausgabe. Sie können sogar sehen, wie viel Zeit auf meinem Computer zwischen den Ausführungen vergangen ist, aber File.Length (), wenn es nicht das erste Mal war und der erste Datenträgerzugriff gewonnen wurde.

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

Wenn ich Ihren Code so ändere, dass eine Datei verwendet wird, auf die über einen absoluten Pfad zugegriffen wird, anstelle einer Ressource, 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

9
tgdavies

Ich bin auf dasselbe Problem gestoßen. Ich musste die Dateigröße und das Änderungsdatum von 90.000 Dateien auf einer Netzwerkfreigabe ermitteln. Java zu verwenden und so minimalistisch wie möglich zu sein, würde sehr lange dauern. (Ich musste die URL aus der Datei und auch den Pfad des Objekts abrufen. Sie variierte also etwas, war aber länger als eine Stunde.) Dann verwendete ich eine native ausführbare Win32-Datei und führte die gleiche Aufgabe aus, indem ich die Datei abspeicherte Pfad, geändert und Größe der Konsole, und ausgeführt, dass von Java. Die Geschwindigkeit war unglaublich. Der native Prozess und meine Zeichenfolgenbehandlung zum Lesen der Daten können über 1000 Elemente pro Sekunde verarbeiten.

Also, obwohl die Leute den obigen Kommentar runtergerankt haben, ist dies eine gültige Lösung und hat mein Problem gelöst. In meinem Fall kannte ich die Ordner, die ich im Voraus benötigte, und das konnte ich 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 Netzwerk-Dateiinformationen zugreifen.

Der Umgang mit Java-Dateien unter Windows ist schrecklich. Der lokale Datenträgerzugriff für Dateien ist jedoch in Ordnung. Es waren nur Netzwerkfreigaben, die die schreckliche Leistung verursachten. Windows könnte Informationen über die Netzwerkfreigabe abrufen und die Gesamtgröße in weniger als einer Minute berechnen.

- Ben

8
Ben Spink

In Reaktion auf den Benchmark von rgrig muss auch die Zeit zum Öffnen/Schließen der Instanzen FileChannel & RandomAccessFile berücksichtigt werden, da diese Klassen einen Stream zum Lesen der Datei öffnen.

Nach dem Ändern des Benchmarks habe ich die folgenden Ergebnisse für 1 Iterationen in einer 85-MB-Datei erhalten:

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 lediglich 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";
        } 
    }
}
8
Karthikeyan

Wenn Sie die Dateigröße mehrerer Dateien in einem Verzeichnis anzeigen möchten, verwenden Sie Files.walkFileTree . Sie erhalten die Größe aus dem BasicFileAttributes, das Sie erhalten.

Dies ist viel schneller, als .length() für das Ergebnis von File.listFiles() aufzurufen oder Files.size() für das Ergebnis von Files.newDirectoryStream() zu verwenden. In meinen Testfällen war es ungefähr 100-mal schneller.

3
Scg

Eigentlich denke ich, dass das "ls" schneller sein kann. Es gibt definitiv einige Probleme in Java beim Abrufen von Dateiinformationen. Leider gibt es für Windows keine vergleichbare sichere Methode für rekursive ls. (cmd.exe DIR/S kann verwirrt werden und Fehler in Endlosschleifen erzeugen)

Unter XP, wenn ich auf einen Server im LAN zugreife, benötige 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, benötige ich mehr als 5 Minuten. Ich habe angefangen, die Zeit zu messen, die zum Ausführen von file.length (), file.lastModified () und file.toURI () benötigt wird. Dabei stellte ich fest, dass diese drei Aufrufe 99% meiner Zeit beanspruchen. 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 des Serverpfads 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 durchlief den gesamten Verzeichnisbaum von 33.000 Dateien in jedem Ordner. Gesamtzeit 7 Sekunden. Java: über 5 Minuten.

Die ursprüngliche Aussage und Frage aus dem OP ist also wahr und gültig. Es ist weniger auffällig, wenn es sich um ein lokales Dateisystem handelt. Ein lokaler Vergleich des Ordners mit 33.000 Elementen dauert in WinMerge 3 Sekunden und in Java 32 Sekunden. Also ist Java versus native eine 10-fache Verlangsamung in diesen rudimentären Tests.

Java 1.6.0_22 (neueste Version), Gigabit LAN und Netzwerkverbindungen, Ping ist kürzer als 1 ms (beide im selben Switch)

Java ist langsam.

2
Ben Spink

Ausgehend von GHads Benchmark gibt es ein paar Punkte, die die Leute angesprochen haben:

1> Wie BalusC erwähnt: stream.available () wird in diesem Fall weitergeleitet.

Da available () ein Schätzwert der Anzahl der Bytes zurückgibt, die aus diesem Eingabestream gelesen (oder übersprungen) werden können, ohne dass dies beim nächsten Aufruf einer Methode für diesen Eingabestream blockiert wird.

Also erstmal die URL dieses Ansatzes entfernen.

2> Wie StuartH bereits erwähnt hat - die Reihenfolge, in der der Test ausgeführt wird, macht auch den Unterschied im Cache aus. Nehmen Sie dies heraus, indem Sie den Test separat ausführen.


Jetzt Test starten:

Wenn CHANNEL einsam läuft:

CHANNEL sum: 59691, per Iteration: 238.764

Wenn man alleine läuft:

LENGTH sum: 48268, per Iteration: 193.072

So sieht die LÄNGE aus, die hier der Gewinner ist:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
2
Gob00st