En recherchant sur Google, je vois que l'utilisation java.io.File#length()
peut être lente.
FileChannel
a unsize()
méthode qui est également disponible.
Existe-t-il un moyen efficace en java d'obtenir la taille du fichier?
En recherchant sur Google, je vois que l'utilisation java.io.File#length()
peut être lente.
FileChannel
a unsize()
méthode qui est également disponible.
Existe-t-il un moyen efficace en java d'obtenir la taille du fichier?
Réponses:
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;
}
}
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).
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));
}
}
}
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
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
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";
}
}
}
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
Si vous voulez la taille de fichier de plusieurs fichiers dans un répertoire, utilisez Files.walkFileTree
. Vous pouvez obtenir la taille du BasicFileAttributes
que 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.
Files.walkFileTree
est disponible sur Android 26+.
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.
À 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();
}