有效地获取文件大小

在谷歌搜索时,我发现使用 java.io.File#length()可能会比较慢。 FileChannel 还有一个可用的 size()方法。

在 Java 中有没有一种有效的方法来获得文件大小?

221449 次浏览

我试着用下面的代码来衡量:

对于 run = 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

对于运行 = 5和迭代 = 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;
}


}

当我修改代码使用一个由绝对路径访问的文件而不是资源访问时,我会得到一个不同的结果(对于1次运行、1次迭代和100,000字节的文件——对于10字节的文件,次数与100,000字节相同)

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

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

URL sum: 294,每次迭代: 294.0

GHad 给出的基准测量了除了获取长度之外的许多其他东西(比如反射、实例化对象等)。如果我们试图摆脱这些东西,那么对于一个电话,我得到以下的时间,以微秒为单位:

file sum___19.0, per Iteration___19.0
raf sum___16.0, per Iteration___16.0
channel sum__273.0, per Iteration__273.0

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

file sum__1767629.0, per Iteration__1.7676290000000001
raf sum___881284.0, per Iteration__0.8812840000000001
channel sum___414286.0, per Iteration__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));
}
}
}

为了响应 rgrig 的基准测试,还需要考虑打开/关闭 FileChannel & RRandom AccessFile 实例所需的时间,因为这些类将打开一个流来读取文件。

在修改基准测试之后,我在一个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";
}
}
}

事实上,我觉得“是”可能更快。在 Java 中肯定有一些关于获取文件信息的问题。遗憾的是,对于 Windows,没有等效的安全递归 ls 方法。(cmd.exe 的 DIR/S 可能会混淆,并在无限循环中产生错误)

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

当我在 Java 中递归地遍历这个过程时,需要花费超过5分钟的时间。我开始测量执行 file.length ()、 file.lastAmendment ()和 file.toURI ()所花费的时间,我发现99% 的时间都被这3个调用占用了。我真正需要做的三个电话。

1000个文件的区别是本地15ms 与服务器上的1800ms。Java 中的服务器路径扫描慢得可笑。如果本地操作系统可以快速扫描同一个文件夹,为什么 Java 不行呢?

作为一个更完整的测试,我使用 XP 上的 WineMerge 来比较修改日期和服务器上文件的大小与本地文件的大小。这将遍历每个文件夹中包含33,000个文件的整个目录树。总共7秒。超过5分钟。

因此,来自 OP 的原始陈述和问题是真实有效的。在处理本地文件系统时不太明显。在 WinMerge 中,对包含33,000个条目的文件夹进行本地比较需要3秒钟,在 Java 中需要32秒钟。因此,在这些基本测试中,Java 与本机测试的速度相比减慢了10倍。

Java 1.6.0 _ 22(最新版本)、千兆局域网(Gigabit LAN)和网络连接,ping 小于1ms (都在同一个交换机中)

Java 很慢。

这篇文章中的所有测试用例都是有缺陷的,因为它们为每个测试的方法访问相同的文件。因此,磁盘缓存可以让测试2和测试3从中受益。为了证明我的观点,我采用了 GHAD 提供的测试用例,改变了枚举的顺序,下面是结果。

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

测试的顺序是输出的顺序。您甚至可以看到在我的机器上所花费的时间在不同的执行之间有所不同,但是 File。长度()当不是第一个时,并且引起第一个磁盘访问获胜。

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

我也遇到了同样的问题。我需要得到一个网络共享上90,000个文件的文件大小和修改日期。使用 Java,并且尽可能简约,这将花费很长的时间。(我需要从文件中获取 URL,以及对象的路径。因此,它有所不同,但超过一个小时。)然后,我使用了一个原生的 Win32可执行文件,执行相同的任务,只是将文件路径、修改和大小转储到控制台,并从 Java 执行该任务。速度太惊人了。本机进程和读取数据的字符串处理每秒可以处理1000多个项。

因此,即使人们排名下降以上评论,这是一个有效的解决方案,并没有解决我的问题。在我的例子中,我提前知道我需要的文件夹的大小,并且我可以在命令行中将它传递给我的 win32应用程序。我从几个小时处理一个目录到几分钟。

这个问题似乎也是 Windows 特有的。OS X 没有同样的问题,可以尽快访问网络文件信息,因为操作系统可以这样做。

Windows 上的 Java 文件处理非常糟糕。不过,可以对文件进行本地磁盘访问。只是网络共享导致了糟糕的表现。Windows 也可以在一分钟内获得网络共享的信息并计算出总体规模。

从 GHad 的基准来看,人们提到了一些问题:

1 > 就像 BalusC 提到的那样: 在这种情况下,stream。

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

所以首先要删除 URL 这种方法。

正如 StuartH 提到的-测试运行的顺序也会影响缓存,所以通过单独运行测试来消除这个差异。


现在开始测试:

当一频道独自运行:

CHANNEL sum: 59691, per Iteration: 238.764

当一个人独自跑步时:

LENGTH sum: 48268, per Iteration: 193.072

看起来长度的那个赢了:

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

如果希望在一个目录中使用多个文件的文件大小,请使用 Files.walkFileTree。您可以从 BasicFileAttributes获得您将收到的大小。

这比对 File.listFiles()的结果调用 .length()或对 Files.newDirectoryStream()的结果使用 Files.size()要快得多。在我的测试案例中,它要快100倍左右。