Java有效地获取文件大小


166

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

Java中是否有一种有效的方法来获取文件大小?


7
您能否提供说File.length()可能很慢的链接?
马特b

1
抱歉,这里是链接javaperformancetuning.com/tips/rawtips.shtml 搜索“诸如File.length()之类的文件信息需要系统调用,并且可能很慢。” 这确实是一个令人困惑的声明,似乎几乎假定它是系统调用。
joshjdevl

25
无论您如何操作,获取文件长度都将需要系统调用。如果通过网络或其他一些非常慢的文件系统,则可能会很慢。没有比File.length()更快的获取方法了,这里“ slow”的定义只是意味着不要不必要地调用它。
jsight

我认为这就是GHad在尝试进行的测试。我的结果是(在ubuntu 8.04上):只有一个访问URL最快。5次运行,50次迭代CHANNEL最快使人迷惑吗?:)虽然出于我的目的,但我只会进行一次访问。虽然很奇怪?我们得到了不同的结果
joshjdevl

1
如果信息在磁盘上而不是在高速缓存中,则此操作可能会非常慢。(例如慢1000倍),除了确保所需的信息始终在缓存中(例如预加载信息并有足够的内存以使其保留在内存中)之外
无能为力

Answers:


102

好吧,我尝试使用下面的代码对其进行度量:

对于运行次数= 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

文件必须缓存对文件系统的调用,而通道和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
不论URL是XP还是linux,似乎URL方式都是最好的单一访问方式。格蕾兹·加德
加德(GHad)

73
stream.available()不返回文件长度。它返回可读取的字节数,而不会阻塞其他流。它的字节数不必与文件长度相同。要从流中获取实际长度,您确实需要读取它(并同时计算读取的字节数)。
BalusC,2009年

11
这个基准是正确的,或者说它的解释是不正确的。如果迭代次数较少,则以后的测试将利用操作系统的文件缓存。在较高的迭代测试中,排名是正确的,但这并不是因为File.length()正在缓存某些东西,而仅仅是因为其他2个选项是基于相同的方法,但做了额外的工作会使它们变慢。
x4u 2011年

2
@Paolo,缓存和优化文件系统访问是操作系统的主要职责之一。faqs.org/docs/linux_admin/buffer-cache.html为了获得良好的基准测试结果,应在每次运行之前清除缓存。
z0r 2012年

3
除了InputStream.available()的javadoc所说的以外,available()方法返回int的事实应该是反对URL方法的一个危险信号。尝试使用3GB的文件,这显然不是确定文件长度的有效方法。
Scrubbie

32

GHad给出的基准除获取长度外,还测量许多其他内容(例如反射,实例化对象等)。如果我们尝试摆脱这些问题,那么一个电话就能获得以下时间(以微秒为单位):

   文件总数___ 19.0,每个迭代___ 19.0
    raf sum ___ 16.0,每个迭代___ 16.0
通道总数__273.0,每个迭代__273.0

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

   文件总和__1767629.0,每个迭代__1.7676290000000001
    raf sum ___ 881284.0,每个迭代__0.8812840000000001
通道sum ___ 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));
    }
  }
}

3
实际上,虽然您说它可以衡量其他方面是正确的,但我的问题应该更清楚。我正在寻找多个文件的文件大小,并且我想要最快的方法。因此,我确实需要考虑对象的创建和开销,因为那是一个真实的场景
joshjdevl

3
大约90%的时间都花在了getResource上。我怀疑您需要使用反射来获取包含某些Java字节码的文件的名称。

20

这篇文章中的所有测试用例都有缺陷,因为它们为每种测试方法访问相同的文件。因此,磁盘缓存将为测试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

9

当我修改代码以使用通过绝对路径而不是资源访问的文件时,得到的结果是不同的(运行1次,迭代1次和生成100,000字节文件– 10字节文件的时间等于100,000字节)

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

频道总数:3626,每个迭代:3626.0

URL总和:294,每次迭代:294.0


9

为了响应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";
        } 
    }
}

8

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

因此,即使人们对以上评论不满意,这也是有效的解决方案,确实解决了我的问题。以我为例,我知道文件夹需要提前准备的大小,可以在命令行中将其传递给我的win32应用程序。我从几个小时处理一个目录到几分钟。

该问题似乎也与Windows有关。OS X没有相同的问题,并且可以像OS一样快地访问网络文件信息。

Windows上的Java文件处理非常糟糕。文件的本地磁盘访问虽然很好。只是网络共享导致了糟糕的性能。Windows也可以在不到一分钟的时间内获取有关网络共享的信息并计算总大小。

-本


3

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

这则快得多调用.length()上的结果File.listFiles(),或使用Files.size()上的结果Files.newDirectoryStream()。在我的测试用例中,它快了大约100倍。


仅供参考,Files.walkFileTree适用于Android 26+。
约书亚·品特

2

实际上,我认为“ ls”可能会更快。Java中肯定有一些有关获取文件信息的问题。不幸的是,Windows没有递归ls的等效安全方法。(cmd.exe的DIR / S可能会陷入混乱并在无限循环中生成错误)

在XP上,访问LAN上的服务器,在Windows中,我花了5秒钟来获取文件夹中文件的数量(33,000)和总大小。

当我用Java递归遍历此过程时,我花了5分钟以上的时间。我开始测量执行file.length(),file.lastModified()和file.toURI()所需的时间,我发现这3个调用占用了我99%的时间。我实际上需要做的3个通话...

1000个文件的差异是本地15ms,而服务器上是1800ms。Java中的服务器路径扫描非常慢。如果本机操作系统可以快速扫描相同的文件夹,那么Java为什么不能?

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

因此,OP的原始陈述和问题是真实且有效的。在处理本地文件系统时,它不太明显。在WinMerge中,对包含33,000个项目的文件夹进行本地比较需要3秒,而在Java中则需要32秒。同样,在这些基本测试中,java与native相比,速度降低了10倍。

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

Java很慢。


2
这也似乎是特定于操作系统的。使用samba从OS X到同一文件夹执行相同的Java应用程序,花费了26秒列出了全部33,000个项目,大小和日期。那么,网络Java在Windows上运行缓慢吗?(OS X也是Java 1.6.0_22。)
Ben Spink,2010年

2

根据GHad的基准,人们提到了一些问题:

1>像BalusC提到的那样:在这种情况下,stream.available()会流动。

因为available()返回估算值可以从此输入流读取(或跳过)的字节数,而不会因下次调用此输入流的方法而阻塞。

所以1st删除URL的这种方法。

2>如StuartH所述-测试运行的顺序也会使缓存有所不同,因此请分别运行测试将其删除。


现在开始测试:

当CHANNEL一个人运行时:

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();
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.