Java高效地获取文件大小

当谷歌search,我看到使用java.io.File#length()可以很慢。 FileChannel也有一个size()方法。

在java中有一种有效的方式来获取文件大小?

那么,我试图用下面的代码来衡量它:

对于运行= 1和迭代= 1,URL方法最快的次数是频道。 我用一些暂停新鲜的约10次运行这个。 所以一次访问,使用URL是我能想到的最快的方式:

 LENGTH sum: 10626, per Iteration: 10626.0 CHANNEL sum: 5535, per Iteration: 5535.0 URL sum: 660, per Iteration: 660.0 

对于运行= 5和迭代= 50,图片绘制不同。

 LENGTH sum: 39496, per Iteration: 157.984 CHANNEL sum: 74261, per Iteration: 297.044 URL sum: 95534, per Iteration: 382.136 

文件必须caching到文件系统的调用,而通道和URL有一些开销。

码:

 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给出的基准除了得到长度之外,还测量了许多其他的东西(比如reflection,实例化对象等等)。 如果我们试图摆脱这些事情,那么对于一个电话,我会得到以下几个微秒:

   文件总和___ 19.0,每迭代___ 19.0
     raf总和___ 16.0,每次迭代___ 16.0
通道总和_273.0,每迭代_273.0

对于100次运行和10000次迭代,我得到:

   文件总和___1767629.0,每迭代_1.7676290000000001
     raf sum ___ 881284.0,per iteration__0.8812840000000001
通道总和___ 414286.0,每迭代___0.414286

我确实运行了下面的修改代码,给出了一个100MB文件的名字作为参数。

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

本文中的所有testing用例都存在缺陷,因为他们访问每个testing方法的相同文件。 因此,磁盘caching踢,其中testing2和3受益。 为了certificate我的观点,我拿了GHAD提供的testing用例,改变了枚举的顺序,下面是结果。

看看结果我认为File.length()真的是赢家。

testing顺序是输出的顺序。 你甚至可以看到我的机器花费的时间在执行之间变化,但是File.Length()不是第一次,而且第一次获得磁盘访问。

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

当我修改代码来使用绝对path而不是资源访问的文件时,我得到了不同的结果(1次运行,1次迭代和100,000字节文件–10字节文件的次数与100,000字节相同)

长度总和:33,每次迭代:33.0

通道总和:3626,每次迭代:3626.0

URL总和:294,每迭代:294.0

为了响应rgrig的基准,还需要考虑打开/closuresFileChannel&RandomAccessFile实例所需的时间,因为这些类将打开一个读取文件的stream。

在修改基准testing之后,我在一个85MB的文件上得到了这些结果:

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

在同一个文件上进行10000次迭代:

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

如果你需要的只是文件大小,file.length()是最快的方法。 如果您打算将文件用于读/写等其他目的,那么皇家空军似乎是一个更好的select。 只要不要忘记closures文件连接:-)

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

我遇到了同样的问题。 我需要在networking共享上获得90,000个文件的文件大小和修改date。 使用Java,并尽可能简约,这将需要很长的时间。 (我需要从文件中获取URL,以及对象的path,所以它有所不同,但超过一个小时)。然后,我使用本机Win32可执行文件,并执行相同的任务,只是转储文件path,修改和大小到控制台,并从Java执行。 速度是惊人的。 本地进程和我的string处理来读取数据可以每秒处理超过1000个项目。

所以即使人们把上面的评论排在前面,这是一个有效的解决scheme,并且解决了我的问题。 在我的情况下,我知道我需要提前的大小的文件夹,我可以通过命令行传递给我的win32应用程序。 我从几个小时开始处理一个目录到几分钟。

这个问题似乎也是Windows特有的。 OS X没有相同的问题,可以像操作系统那样快地访问networking文件信息。

在Windows上的Java文件处理是可怕的。 本地磁盘访问文件是好的,但。 这只是networking共享,造成了糟糕的performance。 Windows可以获取networking共享信息,并在一分钟内计算总大小。

–Ben

如果您想要目录中多个文件的文件大小,请使用Files.walkFileTree 。 您可以从您将收到的BasicFileAttributes中获取大小。

这对Files.size()的结果调用.length()或对Files.size()的结果使用Files.size()Files.newDirectoryStream() 。 在我的testing案例中,它快了大约100倍。

其实我觉得“ls”可能会更快。 Java在处理获取文件信息方面肯定存在一些问题。 不幸的是,没有用于Windows的recursionls的等价安全方法。 (cmd.exe的DIR / S可能会感到困惑,并在无限循环中产生错误)

在XP上,访问局域网上的服务器,需要5秒钟的时间才能得到一个文件夹(33,000)的文件数和总大小。

当我在Java中通过这个recursion迭代时,它花了我5分钟以上。 我开始测量file.length(),file.lastModified()和file.toURI()所花费的时间,我发现我的时间有99%是由这三个调用拍摄的。 我实际上需要做的3个电话…

1000个文件的区别是本地15ms和服务器1800ms。 Java中的服务器path扫描速度非常慢。 如果本机操作系统可以快速扫描相同的文件夹,为什么不能Java?

作为一个更完整的testing,我使用XP上的WineMerge来比较修改date,服务器上文件的大小和本地文件的大小。 这遍历了每个文件夹中33,000个文件的整个目录树。 总时间7秒。 java:超过5分钟。

所以OP的原始陈述和问题是真实的,有效的。 在处理本地文件系统时不太明显。 在WinMerge中进行33,000个文件夹的本地比较需要3秒钟,而在Java中需要32秒。 所以再一次,Java与原生是这些基本testing中的10倍放缓。

Java 1.6.0_22(最新),千兆局域网和networking连接,ping小于1ms(均在同一交换机中)

Java很慢。

从GHAD的基准来看,有一些人提到的问题:

1>像BalusC提到:stream.available()在这种情况下stream动。

因为available()返回可以从该inputstream读取(或跳过)的字节数的估计值 ,而不会因下一次调用该inputstream的方法而被阻塞。

所以第一个删除这个URL的方法。

2>正如StuartH所提到的那样 – testing运行的顺序也使caching不同,所以通过单独运行testing来取出。


现在开始testing:

CHANNEL一个人独自跑

 CHANNEL sum: 59691, per Iteration: 238.764 

当长度单独运行时:

 LENGTH sum: 48268, per Iteration: 193.072 

所以看起来LENGTH是这里的赢家:

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