2010-06-29 48 views
19

我必須處理一個大約200萬個xml的待處理目錄。如何在沒有「內存不足」異常的情況下列出一個200萬個文件目錄

我已經解決了使用隊列在機器和線程之間分配工作的處理過程,一切順利。

但是現在最大的問題是用200萬個文件讀取目錄以便逐漸填滿隊列的瓶頸。

我試過使用File.listFiles()方法,但它給了我一個java out of memory: heap space異常。有任何想法嗎?

+1

對不起,但是什麼操作系統不支持?生活在1950年?我知道各種各樣的工具,例如在windows上(瀏覽器變得很慢),但文件系統支持它。 – TomTom 2010-06-29 08:49:19

+5

@TomTom:FAT32(舊的,但絕不是1950年代,但仍然很常見)每個目錄的文件數量有限制 - 65k。 – 2010-06-29 08:59:37

+0

但是,假設有人使用這個是neglegient - 除了不支持它的設備,然後問題 - 猜猜看 - 不會是「列出文件的問題」。 – TomTom 2010-06-29 09:07:24

回答

11

首先,你有沒有可能使用Java 7?你有一個FileVisitorFiles.walkFileTree,這應該可以在你的記憶限制內工作。

否則,我能想到的唯一的辦法就是使用 File.listFiles(FileFilter filter)一個過濾器,它總是返回 false(確保文件的完整陣列永遠不會保存在內存中),但捕捉到沿着處理的文件方式,並且可能將它們放入生產者/消費者隊列或將文件名寫入磁盤以供稍後遍歷。

另外,如果你控制文件的名稱,或者如果他們在一些不錯的方式命名,你可以使用接受表單上的文件名過濾器處理的塊中的文件file0000000 - filefile0001000然後file0001000 - filefile0002000等上。

如果名字是 而不是這樣以一種很好的方式命名,你可以嘗試根據文件名的散列碼進行過濾,文件名應該是相當均勻地分佈在整數集合上。


更新:嘆息。可能不會工作。剛剛看了一下listFiles的實現:

public File[] listFiles(FilenameFilter filter) { 
    String ss[] = list(); 
    if (ss == null) return null; 
    ArrayList v = new ArrayList(); 
    for (int i = 0 ; i < ss.length ; i++) { 
     if ((filter == null) || filter.accept(this, ss[i])) { 
      v.add(new File(ss[i], this)); 
     } 
    } 
    return (File[])(v.toArray(new File[v.size()])); 
} 

所以它可能會在第一行失敗...總之令人失望。我相信你最好的選擇是把文件放在不同的目錄中。

順便說一句,你能給一個文件名的例子嗎?他們是「可猜測的」嗎?像

for (int i = 0; i < 100000; i++) 
    tryToOpen(String.format("file%05d", i)) 
+0

Java 7現在不是一個選項。 當前我正在嘗試過濾器選項。幸運的是這些文件具有用文件名寫入的層次結構。所以這個選項可以工作。 – Fgblanch 2010-06-29 09:23:30

+1

aioobe有效它沒有工作。我發現文件名是「可猜測的」:)所以我會以相反的方式做到這一點: 生成文件名,然後轉到文件夾,並嘗試達到它們。 非常感謝你的幫助 – Fgblanch 2010-06-29 09:58:28

1

第一次嘗試通過傳遞-Xmx1024m來增加JVM的內存。

+0

我有一種感覺,這不會解決問題,並且JVM稍後會稍微耗盡內存。 – Piskvor 2010-06-29 09:11:42

+0

@Piskvor如果是這樣,我想沒有辦法解決這個問題。無論你用什麼來分析os文件系統,都需要一定的字節數 - 有200萬個文件可能會變得太快。 – InsertNickHere 2010-06-29 09:36:18

+0

您不需要同時將所有數據保存在RAM中。 – Piskvor 2010-06-29 10:42:53

2

爲什麼你在同一個目錄中存儲200萬個文件呢?我可以想象,它已經在操作系統級別降低了訪問速度。

我肯定希望在處理之前將它們分成多個子目錄(例如創建日期/時間)。但如果由於某種原因不可能,是否可以在加工過程中完成?例如。將1000個排隊等待Process1的文件移動到Directory1,另外1000個文件用於Process2到Directory2等。然後,每個進程/線程只能看到爲其分配的(有限數量)文件。

+0

潛入他們自己的問題。我正在考慮OS bash函數。 處理時不可能這樣做,因爲嘗試以編程方式列出目錄時出現異常。 – Fgblanch 2010-06-29 08:50:13

0

請發佈OOM異常的完整堆棧跟蹤以確定瓶頸的位置,以及顯示您看到的行爲的簡短完整的Java程序。

這很可能是因爲您收集了內存中所有200萬條記錄,而且它們不合適。你能增加堆空間嗎?

8

使用File.list()而不是File.listFiles() - 的String對象返回消耗比File對象的內存更少,和(更重要的是,這取決於目錄的位置),它們不包含完整路徑名。

然後,在處理結果時根據需要構造File對象。

但是,這不適用於任意大的目錄。在一個目錄層次結構中組織文件是一個總體上更好的主意,因此沒有一個目錄的數量超過幾千個。

0

如果文件名符合某些規則,則可以使用File.list(filter)而不是File.listFiles來獲取文件列表的可管理部分。

-3

試試這個,它的作品給我,但我沒有那麼多文件...

File dir = new File("directory"); 
String[] children = dir.list(); 
if (children == null) { 
    //Either dir does not exist or is not a directory 
    System.out.print("Directory doesn't exist\n"); 
} 
else { 
    for (int i=0; i<children.length; i++) { 
    // Get filename of file or directory 
    String filename = children[i]; 
} 
+0

它直接什麼是不工作的提問者,他有許多文件 – 2011-09-21 14:18:09

9

如果Java 7的是不是一個選項,這個技巧就可以了(對於UNIX):

Process process = Runtime.getRuntime().exec(new String[]{"ls", "-f", "/path"}); 
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 
String line; 
while (null != (line = reader.readLine())) { 
    if (line.startsWith(".")) 
     continue; 
    System.out.println(line); 
} 

-f參數將加快速度(從man ls):

-f  do not sort, enable -aU, disable -lst 
+1

這不是黑客攻擊,但一種方式來處理有限的Java API) 但也應增加對其他操作系統的支持,並且它將是prima;) – 2011-09-21 14:19:34

2

既然你是在Windows上,好像你SH應該簡單地使用ProcessBuilder啓動「cmd/k dir/b target_directory」之類的東西,捕獲它的輸出並將其路由到一個文件中。然後,您可以一次處理該文件一行,讀取文件名並處理它們。

比從未更好的遲到? ;)

5

如果你可以使用Java 7,可以用這種方法完成,你不會有那些內存不足的問題。

Path path = FileSystems.getDefault().getPath("C:\\path\\with\\lots\\of\\files"); 
     Files.walkFileTree(path, new FileVisitor<Path>() { 
      @Override 
      public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 
       return FileVisitResult.CONTINUE; 
      } 

      @Override 
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 
       // here you have the files to process 
       System.out.println(file); 
       return FileVisitResult.CONTINUE; 
      } 

      @Override 
      public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 
       return FileVisitResult.TERMINATE; 
      } 

      @Override 
      public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 
       return FileVisitResult.CONTINUE; 
      } 
     }); 
-1

您可以使用帶有特殊FilenameFilter的listFiles。 FilenameFilter第一次被髮送到listFiles它接受前1000個文件,然後將它們保存爲已訪問。

下次將FilenameFilter發送到listFiles時,它會忽略前1000個訪問的文件並返回下一個1000,依此類推直到完成。

+0

listFiles中的第一行(即使是使用FilenameFilter)將創建一個字符串數組 - 每個字符串都是目錄中的文件名。另外,由@aioobe指出。 – gjain 2016-09-13 06:24:45

3

您可以使用Apache FileUtils庫來實現。沒有記憶問題。我確實與visualvm檢查。

Iterator<File> it = FileUtils.iterateFiles(folder, null, true); 
    while (it.hasNext()) 
    { 
    File fileEntry = (File) it.next(); 
    } 

希望有幫助。 bye

+1

FileUtils(用2勾選。4)在內部也使用了File#list(),所以與大目錄相同的問題會出現。請注意,#iterateFiles()僅從#listFiles()的結果返回.iterator()。 – ankon 2013-07-31 09:07:19

0

作爲第一種方法,您可以嘗試調整一些JVM內存設置,例如,增加堆大小,因爲它建議甚至使用AggressiveHeap選項。 考慮到大量的文件,這可能沒有幫助,那麼我會建議解決這個問題。在每個文件中創建多個文件名,例如每個文件包含500k個文件名並從中讀取。

0

我開發惡意軟件掃描應用程序時遇到同樣的問題。我的解決方案是執行shell命令列出所有文件。它比通過文件夾瀏覽文件夾的遞歸方法更快。

看到更多的關於shell命令在這裏:http://adbshell.com/commands/adb-shell-ls

 Process process = Runtime.getRuntime().exec("ls -R /"); 
     BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); 

     //TODO: Read the stream to get a list of file path. 
0

這也需要Java 7,但它比Files.walkFileTree答案簡單,如果你只是想列出一個目錄的內容,而不是走在整個樹:

Path dir = Paths.get("/some/directory"); 
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { 
    for (Path path : stream) { 
     handleFile(path.toFile()); 
    } 
} catch (IOException e) { 
    handleException(e); 
} 

DirectoryStream實現是特定於平臺的,從來沒有叫File.list或類似的東西,而不是使用Unix或Windows系統調用,遍歷目錄一個一次進入。

相關問題