java obtenir la taille du fichier efficacement

166

En recherchant sur Google, je vois que l'utilisation java.io.File#length()peut être lente. FileChannela unsize() méthode qui est également disponible.

Existe-t-il un moyen efficace en java d'obtenir la taille du fichier?

joshjdevl
la source
7
pouvez-vous fournir les liens indiquant que File.length () "peut être lent"?
matt b du
1
désolé, voici le lien javaperformancetuning.com/tips/rawtips.shtml recherche "Les informations sur les fichiers telles que File.length () nécessitent un appel système et peuvent être lentes." c'est vraiment une déclaration déroutante, il semble presque supposé que ce serait un appel système.
joshjdevl
25
L'obtention de la longueur du fichier nécessitera un appel système, peu importe comment vous le faites. Il peut être lent si c'est sur un réseau ou un autre système de fichiers très lent. Il n'y a pas de moyen plus rapide de l'obtenir que File.length (), et la définition de "lent" ici signifie simplement ne pas l'appeler inutilement.
jsight
Je pense que c'est ce que GHad essayait de tester ci-dessous. Mes résultats sont (sur ubuntu 8.04): une seule URL d'accès est la plus rapide. 5 courses, 50 itérations CHANNEL est encore plus déroutant? :) pour mes besoins cependant, je ne ferai qu'un seul accès. même si c'est étrange? que nous avons obtenu des résultats différents
joshjdevl
1
Cette opération peut être très lente si les informations se trouvent sur le disque plutôt que dans le cache. (comme 1000x plus lent) cependant, vous ne pouvez pas faire grand chose à ce sujet si ce n'est de vous assurer que les informations dont vous avez besoin sont toujours dans le cache (comme le pré-chargement et avoir suffisamment de mémoire pour qu'elle reste en mémoire)
Peter Lawrey

Réponses:

102

Eh bien, j'ai essayé de le mesurer avec le code ci-dessous:

Pour les exécutions = 1 et les itérations = 1, la méthode URL est la plus rapide la plupart du temps, suivie du canal. Je lance cela avec une pause fraîche environ 10 fois. Donc, pour un accès unique, utiliser l'URL est le moyen le plus rapide auquel je puisse penser:

LENGTH sum: 10626, per Iteration: 10626.0

CHANNEL sum: 5535, per Iteration: 5535.0

URL sum: 660, per Iteration: 660.0

Pour les courses = 5 et les itérations = 50, l'image est différente.

LENGTH sum: 39496, per Iteration: 157.984

CHANNEL sum: 74261, per Iteration: 297.044

URL sum: 95534, per Iteration: 382.136

Le fichier doit mettre en cache les appels au système de fichiers, tandis que les canaux et l'URL ont une surcharge.

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

}
GHad
la source
1
On dirait que la méthode URL est la meilleure pour un accès unique, que ce soit XP ou Linux. Greetz GHad
GHad
73
stream.available()ne renvoie pas la longueur du fichier. Il renvoie la quantité d'octets disponibles pour la lecture sans bloquer les autres flux. Ce n'est pas nécessairement la même quantité d'octets que la longueur du fichier. Pour obtenir la longueur réelle d'un flux, vous devez vraiment le lire (et compter les octets lus pendant ce temps).
BalusC
11
Ce repère est ou plutôt son interprétation n'est pas correcte. Dans le nombre d'itérations faible, les tests ultérieurs tirent parti de la mise en cache des fichiers du système d'exploitation. Dans le test des itérations supérieures, le classement est correct mais pas parce que File.length () met quelque chose en cache, mais simplement parce que les 2 autres options sont basées sur la même méthode mais font un travail supplémentaire qui les ralentit.
x4u
2
@Paolo, la mise en cache et l'optimisation de l'accès au système de fichiers est l'une des principales responsabilités d'un OS. faqs.org/docs/linux_admin/buffer-cache.html Pour obtenir de bons résultats d'analyse comparative, le cache doit être effacé avant chaque exécution.
z0r
3
Au-delà de ce que dit le javadoc pour InputStream.available (), le fait que la méthode available () renvoie un int devrait être un drapeau rouge contre l'approche URL. Essayez-le avec un fichier de 3 Go et il sera évident que ce n'est pas un moyen valide de déterminer la longueur du fichier.
Scrubbie
32

Le benchmark donné par GHad mesure beaucoup d'autres choses (comme la réflexion, l'instanciation d'objets, etc.) en plus d'obtenir la longueur. Si nous essayons de nous débarrasser de ces choses, pour un appel, j'obtiens les temps suivants en microsecondes:

   somme des fichiers ___ 19,0, par itération ___ 19,0
    raf somme ___ 16,0, par itération ___ 16,0
somme des canaux__273,0, par itération__273,0

Pour 100 exécutions et 10000 itérations, j'obtiens:

   somme de fichier__1767629.0, par itération__1.7676290000000001
    raf somme ___ 881284.0, par itération__0.8812840000000001
somme des canaux ___ 414286,0, par itération__0,414286

J'ai exécuté le code modifié suivant en donnant comme argument le nom d'un fichier de 100 Mo.

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));
    }
  }
}
basilikode
la source
3
en fait, même si vous avez raison de dire qu'il mesure d'autres aspects, je devrais être plus clair dans ma question. Je cherche à obtenir la taille de fichier de plusieurs fichiers, et je veux le moyen le plus rapide possible. donc je dois vraiment prendre en compte la création d'objet et les frais généraux, car c'est un vrai scénario
joshjdevl
3
Environ 90% du temps est consacré à cette chose getResource. Je doute que vous ayez besoin d'utiliser la réflexion pour obtenir le nom d'un fichier contenant du bytecode Java.
20

Tous les cas de test de cet article sont imparfaits car ils accèdent au même fichier pour chaque méthode testée. Ainsi, la mise en cache du disque démarre dont les tests 2 et 3 bénéficient. Pour prouver mon point, j'ai pris le cas de test fourni par GHAD et changé l'ordre d'énumération et ci-dessous sont les résultats.

En regardant le résultat, je pense que File.length () est vraiment le gagnant.

L'ordre de test est l'ordre de sortie. Vous pouvez même voir le temps passé sur ma machine varié entre les exécutions, mais File.Length () lorsqu'il n'est pas le premier, et le premier accès au disque a gagné.

---
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
StuartH
la source
9

Lorsque je modifie votre code pour utiliser un fichier accessible par un chemin absolu au lieu d'une ressource, j'obtiens un résultat différent (pour 1 exécution, 1 itération et un fichier de 100 000 octets - les temps pour un fichier de 10 octets sont identiques à 100 000 octets )

LONGUEUR somme: 33, par itération: 33,0

CHANNEL somme: 3626, par Itération: 3626.0

Somme d'URL: 294, par itération: 294,0

tgdavies
la source
9

En réponse au benchmark de rgrig, le temps nécessaire pour ouvrir / fermer les instances FileChannel & RandomAccessFile doit également être pris en compte, car ces classes ouvriront un flux pour lire le fichier.

Après avoir modifié le benchmark, j'ai obtenu ces résultats pour 1 itérations sur un fichier de 85 Mo:

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

Pour 10000 itérations sur le même fichier:

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

Si vous n'avez besoin que de la taille du fichier, file.length () est le moyen le plus rapide de le faire. Si vous prévoyez d'utiliser le fichier à d'autres fins comme la lecture / l'écriture, la RAF semble être un meilleur pari. N'oubliez pas de fermer la connexion de fichier :-)

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

J'ai rencontré ce même problème. J'avais besoin d'obtenir la taille du fichier et la date de modification de 90 000 fichiers sur un partage réseau. Utiliser Java, et être aussi minimaliste que possible, cela prendrait beaucoup de temps. (J'avais besoin d'obtenir l'URL du fichier, ainsi que le chemin de l'objet. Donc, cela variait quelque peu, mais plus d'une heure.) J'ai ensuite utilisé un exécutable Win32 natif, et j'ai fait la même tâche, en vidant simplement le fichier chemin, modifié et taille vers la console, et exécuté à partir de Java. La vitesse était incroyable. Le processus natif et ma gestion des chaînes pour lire les données pourraient traiter plus de 1000 éléments par seconde.

Donc, même si les gens ont classé le commentaire ci-dessus, c'est une solution valable et a résolu mon problème. Dans mon cas, je connaissais à l'avance les dossiers dont j'avais besoin, et je pouvais les transmettre dans la ligne de commande à mon application win32. Je suis passé d'heures à traiter un annuaire en minutes.

Le problème semblait également être spécifique à Windows. OS X n'avait pas le même problème et pouvait accéder aux informations sur les fichiers réseau aussi rapidement que le système d'exploitation le pouvait.

La gestion des fichiers Java sous Windows est terrible. L'accès au disque local pour les fichiers est bien cependant. Ce ne sont que les partages réseau qui ont causé les performances terribles. Windows pourrait également obtenir des informations sur le partage réseau et calculer la taille totale en moins d'une minute.

--Ben

Ben Spink
la source
3

Si vous voulez la taille de fichier de plusieurs fichiers dans un répertoire, utilisez Files.walkFileTree. Vous pouvez obtenir la taille du BasicFileAttributesque vous recevrez.

C'est beaucoup plus rapide que d'appeler .length()le résultat de File.listFiles()ou d'utiliser Files.size()le résultat de Files.newDirectoryStream(). Dans mes cas de test, c'était environ 100 fois plus rapide.

Scg
la source
FYI, Files.walkFileTreeest disponible sur Android 26+.
Joshua Pinter
2

En fait, je pense que le "ls" peut être plus rapide. Il y a certainement des problèmes en Java concernant l'obtention d'informations sur les fichiers. Malheureusement, il n'existe pas de méthode sûre équivalente de ls récursif pour Windows. (Le DIR / S de cmd.exe peut devenir confus et générer des erreurs dans des boucles infinies)

Sur XP, en accédant à un serveur sur le LAN, il me faut 5 secondes sous Windows pour obtenir le nombre de fichiers dans un dossier (33 000), et la taille totale.

Lorsque j'itère récursivement à travers cela en Java, cela me prend plus de 5 minutes. J'ai commencé à mesurer le temps nécessaire pour faire file.length (), file.lastModified () et file.toURI () et ce que j'ai trouvé, c'est que 99% de mon temps est pris par ces 3 appels. Les 3 appels que j'ai réellement besoin de faire ...

La différence pour 1000 fichiers est de 15 ms en local par rapport à 1 800 ms sur le serveur. L'analyse du chemin du serveur en Java est ridiculement lente. Si le système d'exploitation natif peut analyser rapidement ce même dossier, pourquoi Java ne le peut-il pas?

Comme test plus complet, j'ai utilisé WineMerge sur XP pour comparer la date modifiée et la taille des fichiers sur le serveur par rapport aux fichiers localement. C'était itérer sur toute l'arborescence de répertoires de 33 000 fichiers dans chaque dossier. Temps total, 7 secondes. java: plus de 5 minutes.

Donc, la déclaration et la question originales du PO sont vraies et valides. C'est moins perceptible lorsqu'il s'agit d'un système de fichiers local. Faire une comparaison locale du dossier avec 33 000 éléments prend 3 secondes dans WinMerge et prend 32 secondes localement en Java. Encore une fois, java versus native est un ralentissement 10x dans ces tests rudimentaires.

Java 1.6.0_22 (dernier), Gigabit LAN et connexions réseau, le ping est inférieur à 1 ms (les deux dans le même commutateur)

Java est lent.

Ben Spink
la source
2
Cela semble également être spécifique au système d'exploitation. Faire la même application Java après le même dossier depuis OS X en utilisant samba, il a fallu 26 secondes pour lister les 33000 éléments, tailles et dates entiers. Donc, le Java réseau est juste lent sous Windows? (OS X était aussi java 1.6.0_22.)
Ben Spink
2

À partir de la référence de GHad, il y a quelques problèmes que les gens ont mentionnés:

1> Comme BalusC l'a mentionné: stream.available () est déroulé dans ce cas.

Parce que available () renvoie une estimation du nombre d'octets qui peuvent être lus (ou ignorés) à partir de ce flux d'entrée sans blocage par le prochain appel d'une méthode pour ce flux d'entrée.

Donc, 1er pour supprimer l'URL cette approche.

2> Comme StuartH l'a mentionné - l'ordre d'exécution du test fait également la différence du cache, alors supprimez-le en exécutant le test séparément.


Maintenant, commencez le test:

Lorsque CHANNEL one fonctionne seul:

CHANNEL sum: 59691, per Iteration: 238.764

Lorsque LENGTH une course seule:

LENGTH sum: 48268, per Iteration: 193.072

On dirait donc que le LENGTH est le gagnant ici:

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