在谷歌搜索中,我发现使用java.io.File#length()
可能会很慢。同时,FileChannel
也有一个可用的size()
方法。
在Java中有没有一种高效的方法来获取文件大小?
在谷歌搜索中,我发现使用java.io.File#length()
可能会很慢。同时,FileChannel
也有一个可用的size()
方法。
在Java中有没有一种高效的方法来获取文件大小?
好的,我尝试使用以下代码对其进行测量:
当runs = 1且iterations = 1时,URL方法大多数情况下是最快的,其次是channel。我做了大约10次暂停的新测试。因此,对于一次访问,使用URL是我能想到的最快的方法:
LENGTH sum: 10626, per Iteration: 10626.0
CHANNEL sum: 5535, per Iteration: 5535.0
URL sum: 660, per Iteration: 660.0
当runs = 5和iterations = 50时,图片呈现出不同的绘制效果。
LENGTH sum: 39496, per Iteration: 157.984
CHANNEL sum: 74261, per Iteration: 297.044
URL sum: 95534, per Iteration: 382.136
文件必须缓存对文件系统的调用,而通道和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;
}
}
stream.available()
方法不会返回文件长度,而是返回当前可供读取的字节数,而不会阻塞其他流的读取。它返回的字节数未必与文件长度相同。如果想要得到流的实际长度,你需要读取(并在读取时统计字节数)。 - BalusCGHad给出的基准测试测量了许多其他内容(例如反射、实例化对象等),而不仅仅是获取长度。如果我们试图摆脱这些东西,那么对于一个调用,我得到以下微秒时间:
文件总和___19.0,每次迭代___19.0 raf总和___16.0,每次迭代___16.0 通道总和__273.0,每次迭代__273.0
对于100次运行和10000次迭代,我得到:
文件总和__1767629.0,每次迭代__1.7676290000000001 raf总和___881284.0,每次迭代__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));
}
}
}
这篇文章中的所有测试用例都存在缺陷,因为它们针对每个被测试的方法访问同一个文件。因此磁盘缓存会发挥作用,从而使得测试2和3受益。为了证明我的观点,我采用了GHAD提供的测试用例,并更改了枚举的顺序,以下是结果。
从结果来看,我认为File.length()才是真正的赢家。
测试的顺序就是输出的顺序。甚至可以看到在我的机器上执行时所花费的时间会因不同的执行而有所变化,但当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
回应rgrig的基准测试,需要考虑打开/关闭FileChannel和RandomAccessFile实例所需的时间,因为这些类将打开一个流以读取文件。
修改基准测试后,我得到了对于85MB文件的1次迭代的以下结果:
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()是最快的方法。如果你打算将文件用于其他目的,如读写操作,则RAF似乎更好。只是不要忘记关闭文件连接:-)
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";
}
}
}
Files.walkFileTree
。您可以从收到的BasicFileAttributes
中获得大小。File.listFiles()
的结果上调用.length()
或在Files.newDirectoryStream()
的结果上使用Files.size()
要快得多。在我的测试案例中,它快了大约100倍。Files.walkFileTree
仅适用于 Android 26+。 - Joshua Pinter根据GHad的基准测试,有一些问题被提到:
1>像BalusC所提到的那样:在这种情况下,stream.available()存在问题。
因为available()返回一个估计值,即在不阻塞此输入流的下一次调用方法的情况下可以从该输入流中读取(或跳过)的字节数。
因此,首先要排除此方法以删除URL。
2>正如StuartH所提到的 - 测试运行的顺序也会造成缓存差异,因此需要单独运行测试来消除这一影响。
现在开始测试:
当CHANNEL 1单独运行时:
CHANNEL sum: 59691, per Iteration: 238.764
当LENGTH只有一个运行时:
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();
}