我必須處理一個大約200萬個xml的待處理目錄。如何在沒有「內存不足」異常的情況下列出一個200萬個文件目錄
我已經解決了使用隊列在機器和線程之間分配工作的處理過程,一切順利。
但是現在最大的問題是用200萬個文件讀取目錄以便逐漸填滿隊列的瓶頸。
我試過使用File.listFiles()
方法,但它給了我一個java out of memory: heap space
異常。有任何想法嗎?
我必須處理一個大約200萬個xml的待處理目錄。如何在沒有「內存不足」異常的情況下列出一個200萬個文件目錄
我已經解決了使用隊列在機器和線程之間分配工作的處理過程,一切順利。
但是現在最大的問題是用200萬個文件讀取目錄以便逐漸填滿隊列的瓶頸。
我試過使用File.listFiles()
方法,但它給了我一個java out of memory: heap space
異常。有任何想法嗎?
首先,你有沒有可能使用Java 7?你有一個FileVisitor
和Files.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))
第一次嘗試通過傳遞-Xmx1024m來增加JVM的內存。
我有一種感覺,這不會解決問題,並且JVM稍後會稍微耗盡內存。 – Piskvor 2010-06-29 09:11:42
@Piskvor如果是這樣,我想沒有辦法解決這個問題。無論你用什麼來分析os文件系統,都需要一定的字節數 - 有200萬個文件可能會變得太快。 – InsertNickHere 2010-06-29 09:36:18
您不需要同時將所有數據保存在RAM中。 – Piskvor 2010-06-29 10:42:53
爲什麼你在同一個目錄中存儲200萬個文件呢?我可以想象,它已經在操作系統級別降低了訪問速度。
我肯定希望在處理之前將它們分成多個子目錄(例如創建日期/時間)。但如果由於某種原因不可能,是否可以在加工過程中完成?例如。將1000個排隊等待Process1的文件移動到Directory1,另外1000個文件用於Process2到Directory2等。然後,每個進程/線程只能看到爲其分配的(有限數量)文件。
潛入他們自己的問題。我正在考慮OS bash函數。 處理時不可能這樣做,因爲嘗試以編程方式列出目錄時出現異常。 – Fgblanch 2010-06-29 08:50:13
請發佈OOM異常的完整堆棧跟蹤以確定瓶頸的位置,以及顯示您看到的行爲的簡短完整的Java程序。
這很可能是因爲您收集了內存中所有200萬條記錄,而且它們不合適。你能增加堆空間嗎?
使用File.list()
而不是File.listFiles()
- 的String
對象返回消耗比File
對象的內存更少,和(更重要的是,這取決於目錄的位置),它們不包含完整路徑名。
然後,在處理結果時根據需要構造File
對象。
但是,這不適用於任意大的目錄。在一個目錄層次結構中組織文件是一個總體上更好的主意,因此沒有一個目錄的數量超過幾千個。
如果文件名符合某些規則,則可以使用File.list(filter)
而不是File.listFiles
來獲取文件列表的可管理部分。
試試這個,它的作品給我,但我沒有那麼多文件...
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];
}
它直接什麼是不工作的提問者,他有許多文件 – 2011-09-21 14:18:09
如果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
這不是黑客攻擊,但一種方式來處理有限的Java API) 但也應增加對其他操作系統的支持,並且它將是prima;) – 2011-09-21 14:19:34
既然你是在Windows上,好像你SH應該簡單地使用ProcessBuilder啓動「cmd/k dir/b target_directory」之類的東西,捕獲它的輸出並將其路由到一個文件中。然後,您可以一次處理該文件一行,讀取文件名並處理它們。
比從未更好的遲到? ;)
如果你可以使用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;
}
});
您可以使用帶有特殊FilenameFilter的listFiles。 FilenameFilter第一次被髮送到listFiles它接受前1000個文件,然後將它們保存爲已訪問。
下次將FilenameFilter發送到listFiles時,它會忽略前1000個訪問的文件並返回下一個1000,依此類推直到完成。
listFiles中的第一行(即使是使用FilenameFilter)將創建一個字符串數組 - 每個字符串都是目錄中的文件名。另外,由@aioobe指出。 – gjain 2016-09-13 06:24:45
您可以使用Apache FileUtils庫來實現。沒有記憶問題。我確實與visualvm檢查。
Iterator<File> it = FileUtils.iterateFiles(folder, null, true);
while (it.hasNext())
{
File fileEntry = (File) it.next();
}
希望有幫助。 bye
FileUtils(用2勾選。4)在內部也使用了File#list(),所以與大目錄相同的問題會出現。請注意,#iterateFiles()僅從#listFiles()的結果返回.iterator()。 – ankon 2013-07-31 09:07:19
作爲第一種方法,您可以嘗試調整一些JVM內存設置,例如,增加堆大小,因爲它建議甚至使用AggressiveHeap選項。 考慮到大量的文件,這可能沒有幫助,那麼我會建議解決這個問題。在每個文件中創建多個文件名,例如每個文件包含500k個文件名並從中讀取。
我開發惡意軟件掃描應用程序時遇到同樣的問題。我的解決方案是執行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.
這也需要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系統調用,遍歷目錄一個一次進入。
對不起,但是什麼操作系統不支持?生活在1950年?我知道各種各樣的工具,例如在windows上(瀏覽器變得很慢),但文件系統支持它。 – TomTom 2010-06-29 08:49:19
@TomTom:FAT32(舊的,但絕不是1950年代,但仍然很常見)每個目錄的文件數量有限制 - 65k。 – 2010-06-29 08:59:37
但是,假設有人使用這個是neglegient - 除了不支持它的設備,然後問題 - 猜猜看 - 不會是「列出文件的問題」。 – TomTom 2010-06-29 09:07:24