2008-09-22 86 views
153

虽然使用谷歌搜索,但我发现使用java.io.File#length()可能会很慢。 FileChannel有一个size()方法,也可用。java有效获取文件大小

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

+7

你能否提供链接说File.length()“可以很慢”? – 2008-09-22 19:02:37

+1

对不起,这里是链接 http://www.javaperformancetuning.com/tips/rawtips.shtml 搜索 “文件信息,如File.length()需要系统调用,可能会很慢。” 这真是一个令人困惑的陈述,似乎几乎认为这将是一个系统调用。 – joshjdevl 2008-09-22 19:53:25

+24

无论您如何操作,获取文件长度都需要系统调用。如果它通过网络或其他非常慢的文件系统,速度可能会很慢。没有比File.length()更快的方法来获取它,而这里“慢”的定义只是意味着不要不必要地调用它。 – jsight 2008-09-22 20:18:12

回答

95

好,我试图用下面的代码来测量它:

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

} 
9

当我修改代码以使用由绝对路径,而不是资源访问的文件,我得到不同的结果(1点运行,1次迭代和10万字节的文件 - 次为一10字节的文件是相同的100,000字节)

LENGTH总和:33,每次迭代:33.0

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

URL总数:294,迭代次数:294.0

31

除了获取长度以外,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)); 
    } 
    } 
} 
8

针对rgrig的基准,开/所花费的时间关闭FileChannel & RandomAccessFile的情况下还需要考虑,因为这些类将打开一个流读取文件。

修改基准后,我得到了这些结果为1次迭代上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()是最快的方法。如果您打算将文件用于读/写等其他目的,那么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"; 
     } 
    } 
} 
2

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

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

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

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

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

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

爪哇1.6.0_22(最新),千兆LAN,和网络连接,平是小于1ms(均在相同的开关)

Java是缓慢的。

16

这篇文章中的所有测试用例都存在缺陷,因为他们访问每个测试方法的相同文件。因此,测试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 
8

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

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

该问题似乎也是Windows特定的。OS X没有相同的问题,并且可以像操作系统那样快速地访问网络文件信息。

Java在Windows上进行文件处理非常糟糕。本地磁盘访问文件虽然很好。这只是网络共享造成了糟糕的表现。 Windows可以获取网络共享信息并在一分钟内计算总大小。

--Ben

2

从GHAD的基准,有几个问题人提到:

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

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

所以首先删除这个URL的方法。

2>正如StuartH所述 - 测试运行的顺序也会导致高速缓存不同,因此请单独运行测试以取得测试结果。


现在开始测试:

当CHANNEL一个单独运行:

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

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

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