Pendant la recherche sur Google, je constate que l’utilisation de Java.io.File#length()
peut être lente. FileChannel
a une méthode size()
également disponible.
Existe-t-il un moyen efficace dans Java d'obtenir la taille du fichier?
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 d'URL est la plus rapide suivie de la chaîne. Je lance ceci 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 les 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;
}
}
Le repère donné par GHad mesure beaucoup d'autres choses (telles que la réflexion, l'instanciation d'objets, etc.) en plus de la longueur. Si nous essayons de nous débarrasser de ces choses, alors, pour un appel, je reçois les temps suivants en microsecondes:
somme de fichier ___ 19.0, par itération ___ 19.0 raf somme ___ 16.0, par itération ___ 16.0 somme de canal__273.0, par itération__273.0
Pour 100 courses et 10000 itérations, je reçois:
file sum__1767629.0, par itération__1,7676290000000001 raf sum ___ 881284.0, par itération__0,8812840000000001 channel sum ___ 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));
}
}
}
Tous les scénarios de test de ce post sont erronés car ils accèdent au même fichier pour chaque méthode testée. La mise en cache sur disque est donc bénéfique aux tests 2 et 3. Pour prouver mon argument, j'ai pris le cas test fourni par GHAD et changé l'ordre de dénombrement. Les résultats sont indiqués ci-dessous.
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 que le temps pris sur ma machine varie entre les exécutions, mais File.Length () lorsqu'il n'est pas premier et que le premier accès au disque est 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
Lorsque je modifie votre code pour utiliser un fichier accédé 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
CANAL somme: 3626, par itération: 3626.0
Somme URL: 294, par itération: 294.0
J'ai rencontré le 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. En utilisant Java, et 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. Cela a donc varié quelque peu, mais plus d'une heure.) J'ai ensuite utilisé un exécutable Win32 natif et j'ai effectué la même tâche, en vidant simplement le fichier. chemin, modifié et la taille à la console, et exécuté à partir de Java. La vitesse était incroyable. Le processus natif et la manipulation de ma chaîne 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 au-dessus, c'est une solution valable et a résolu mon problème. Dans mon cas, je connaissais les dossiers dont j'avais besoin à l'avance, et je pouvais le transmettre en ligne de commande à mon application win32. Je suis passé d'heures à traiter un répertoire en quelques 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 du réseau aussi rapidement que le système d'exploitation pouvait le faire.
Java La gestion des fichiers sous Windows est terrible. L'accès au disque local pour les fichiers est correct. Ce sont juste les partages réseau qui ont provoqué cette terrible performance. Windows pourrait également obtenir des informations sur le partage réseau et calculer la taille totale en moins d’une minute.
--Ben
En réponse au point de repère 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 la lecture du fichier.
Après avoir modifié le repère, 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 10 000 itérations sur le même fichier:
file totalTime: 80074000 (80 ms)
raf totalTime: 295417000 (295 ms)
channel totalTime: 368239000 (368 ms)
Si tout ce dont vous avez besoin est la taille du fichier, file.length () est le moyen le plus rapide de le faire. Si vous envisagez d’utiliser le fichier à d’autres fins, telles que la lecture/é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";
}
}
}
Si vous voulez la taille de plusieurs fichiers dans un répertoire, utilisez Files.walkFileTree
. Vous pouvez obtenir la taille à partir de la BasicFileAttributes
que vous recevrez.
C'est beaucoup plus rapide que d'appeler .length()
sur le résultat de File.listFiles()
ou d'utiliser Files.size()
sur le résultat de Files.newDirectoryStream()
. Dans mes cas de test, il était environ 100 fois plus rapide.
En fait, je pense que le "ls" peut être plus rapide. Il y a certainement des problèmes dans Java pour obtenir des informations sur les fichiers. Malheureusement, il n’existe aucune méthode sûre équivalente de ls récursive pour Windows. (Le répertoire DIR/S de cmd.exe peut être dérouté et générer des erreurs dans des boucles infinies)
Sur XP, pour accéder à un serveur du réseau local, il me faut 5 secondes sous Windows pour obtenir le nombre de fichiers d'un dossier (33 000) et la taille totale.
Lorsque j’ai itéré récursivement dans Java, cela me prend plus de 5 minutes. J'ai commencé à mesurer le temps qu'il faut pour exécuter file.length (), file.lastModified () et file.toURI (). Ce que j'ai découvert, c'est que ces 3 appels prennent 99% de mon temps. Les 3 appels que je dois réellement faire ...
La différence pour 1 000 fichiers est de 15 ms en local par rapport à 1 800 ms sur le serveur. L'analyse du chemin du serveur dans Java est ridiculement lente. Si le système d'exploitation natif peut analyser rapidement le même dossier, pourquoi pas Java?
Pour un test plus complet, j'ai utilisé WineMerge sur XP pour comparer la date de modification et la taille des fichiers sur le serveur par rapport aux fichiers localement. Cela parcourait l’ensemble de l’arborescence de 33 000 fichiers de chaque dossier. Temps total, 7 secondes. Java: plus de 5 minutes.
La déclaration et la question originales du PO sont donc vraies et valides. C'est moins visible 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 32 secondes localement en Java. Encore une fois, Java par rapport à native est un ralentissement de 10x dans ces tests rudimentaires.
Java 1.6.0_22 (dernière version), réseau local Gigabit et connexions réseau, ping est inférieur à 1 ms (les deux dans le même commutateur)
Java est lent.
Selon GHad, il existe quelques problèmes que les gens ont mentionnés:
1> Comme mentionné par BalusC: stream.available () est écoulé dans ce cas.
Parce que available () renvoie une estimation du nombre d'octets pouvant ê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 de cette approche.
2> Comme StuartH l’a mentionné - l’ordre dans lequel le test est exécuté fait également la différence entre les caches, aussi supprimez-le en exécutant le test séparément.
Maintenant, commencez le test:
Quand CHANNEL on tourne seul:
CHANNEL sum: 59691, per Iteration: 238.764
Quand LONGUEUR on court seul:
LENGTH sum: 48268, per Iteration: 193.072
Donc, on dirait que le LONGUEUR est le gagnant ici:
@Override
public long getResult() throws Exception {
File me = new File(FileSizeBench.class.getResource(
"FileSizeBench.class").getFile());
return me.length();
}