java有效地获取文件大小

Posted

技术标签:

【中文标题】java有效地获取文件大小【英文标题】:java get file size efficiently 【发布时间】:2010-09-12 02:35:35 【问题描述】:

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

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

【问题讨论】:

你能提供链接说 File.length() “可能很慢”吗? 对不起,这里是链接javaperformancetuning.com/tips/rawtips.shtml 搜索“文件信息如 File.length() 需要系统调用,可能很慢。”这真是一个令人困惑的陈述,似乎几乎假设它是一个系统调用。 无论你怎么做,获取文件长度都需要系统调用。如果它通过网络或其他一些非常慢的文件系统,它可能会很慢。没有比 File.length() 更快的方法了,这里“慢”的定义就是不要不必要地调用它。 我认为这就是 GHad 在下面尝试测试的内容。我的结果是(在 ubuntu 8.04 上):只有一个访问 URL 是最快的。 5 次运行,50 次迭代 CHANNEL 是最快的混淆了吗? :) 不过,出于我的目的,我只会做一次访问。虽然很奇怪?我们得到了不同的结果 如果信息在磁盘而不是缓存中,这个操作可能会很慢。 (比如慢 1000 倍)但是,除了确保您需要的信息始终在缓存中(例如预加载它并有足够的内存使其保留在内存中)之外,您几乎无能为力。 【参考方案1】:

好吧,我试着用下面的代码来衡量它:

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


【讨论】:

似乎 URL 方式是单次访问的最佳方式,无论是 XP 还是 linux。 Greetz GHad stream.available() 不返回文件长度。它返回在不阻塞其他流的情况下可供读取的字节数。它不一定与文件长度相同的字节数。要从流中获取实际长度,您确实需要读取它(同时计算读取的字节数)。 这个基准是或者说它的解释是不正确的。在低迭代计数中,后面的测试利用了操作系统的文件缓存。在更高的迭代测试中,排名是正确的,但不是因为 File.length() 正在缓存某些东西,而仅仅是因为其他 2 个选项基于相同的方法,但做了额外的工作,从而减慢了它们的速度。 @Paolo,缓存和优化文件系统访问是操作系统的主要职责之一。 faqs.org/docs/linux_admin/buffer-cache.html 为获得良好的基准测试结果,应在每次运行前清除缓存。 除了 InputStream.available() 的 javadoc 所说的之外,available() 方法返回一个 int 的事实应该是对 URL 方法的危险信号。用 3GB 的文件试一下,很明显这不是确定文件长度的有效方法。【参考方案2】:

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 通道总和___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));
    
  

【讨论】:

实际上,虽然您说它衡量其他方面是正确的,但我的问题应该更清楚。我正在寻找多个文件的文件大小,并且我想要最快的方法。所以我确实需要考虑对象创建和开销,因为这是一个真实的场景 大约 90% 的时间都花在了 getResource 上。我怀疑您是否需要使用反射来获取包含一些 Java 字节码的文件的名称。【参考方案3】:

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

【讨论】:

【参考方案4】:

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

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

CHANNEL 总和:3626,每次迭代:3626.0

网址总和:294,每次迭代:294.0

【讨论】:

【参考方案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";
         
    

【讨论】:

【参考方案6】:

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

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

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

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

--本

【讨论】:

【参考方案7】:

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

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

【讨论】:

仅供参考,Files.walkFileTree 适用于 android 26+。【参考方案8】:

实际上,我认为“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 个文件的区别是本地 15 毫秒与服务器上 1800 毫秒。 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 很慢。

【讨论】:

这似乎也是特定于操作系统的。使用 samba 从 OS X 中对同一个文件夹执行相同的 java 应用程序,需要 26 秒才能列出全部 33,000 个项目、大小和日期。那么网络 Java 在 Windows 上只是慢吗? (OS X 也是 java 1.6.0_22。)【参考方案9】:

从 GHad 的基准测试中,人们提到了几个问题:

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

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

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

2>正如 StuartH 提到的 - 测试运行的顺序也会使缓存有所不同,所以通过单独运行测试来消除它。


现在开始测试:

当 CHANNEL one 单独运行时:

CHANNEL sum: 59691, per Iteration: 238.764

当 LENGTH one 单独跑时:

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

【讨论】:

以上是关于java有效地获取文件大小的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 7 中可移植地获取文件存储的块大小?

Spark DataSet 有效地获取整行的长度大小

java file 获取文件大小 是啥单位

java怎么获取inputstream的大小

java获取文件大小

java获取文件大小的方法