2008-09-22 85 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倍。