[英]How to list a 2 million files directory in java without having an “out of memory” exception
我必須處理一個大約200萬xml的目錄進行處理。
我已經解決了使用隊列在機器和線程之間分配工作的處理,一切正常。
但現在最大的問題是用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))
如果Java 7不是一個選項,那么這個hack將起作用(對於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
使用File.list()
而不是File.listFiles()
- 它返回的String
對象消耗的內存少於File
對象,並且(更重要的是,取決於目錄的位置)它們不包含完整路徑名。
然后,在處理結果時根據需要構造File
對象。
但是,這對於任意大的目錄也不起作用。 在目錄層次結構中組織文件是一個總體上更好的想法,這樣任何單個目錄都不會有超過幾千個條目。
如果您可以使用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;
}
});
您可以使用Apache FileUtils庫執行此操作。 沒有記憶問題。 我確實檢查過visualvm。
Iterator<File> it = FileUtils.iterateFiles(folder, null, true);
while (it.hasNext())
{
File fileEntry = (File) it.next();
}
希望有所幫助。 再見
由於你在Windows上,似乎你應該只使用ProcessBuilder來啟動類似“cmd / k dir / b target_directory”的東西,捕獲它的輸出,並將其路由到文件中。 然后,您可以一次處理該文件,讀取文件名並處理它們。
遲到總比不到好? ;)
為什么要在同一目錄中存儲200萬個文件呢? 我可以想象它已經在操作系統級別上嚴重降低了訪問速度。
我肯定希望在處理之前將它們分成子目錄(例如,按創建的日期/時間)。 但如果由於某種原因不可能,那么可以在處理過程中完成嗎? 例如,將排隊等待Process1的1000個文件移動到Directory1,將Process2的另外1000個文件移動到Directory2等。然后每個進程/線程只看到為其分配的(有限數量)文件。
這也需要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系統調用,一次迭代一個目錄。
首先,您可以嘗試通過-Xmx1024m來增加JVM的內存,例如
請發布OOM異常的完整堆棧跟蹤以確定瓶頸的位置,以及顯示您看到的行為的簡短,完整的Java程序。
這很可能是因為你收集了內存中的所有200萬個條目,並且它們不合適。 你能增加堆空間嗎?
如果文件名遵循某些規則,則可以使用File.list(filter)
而不是File.listFiles
來獲取文件列表的可管理部分。
我開發惡意軟件掃描應用程序時遇到了同樣的問題。 我的解決方案是執行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.
您可以將listFiles與特殊的FilenameFilter一起使用。 第一次將FilenameFilter發送到listFiles時,它接受前1000個文件,然后將它們保存為訪問過的文件。
下次將FilenameFilter發送到listFiles時,它會忽略前1000個訪問文件並返回下一個1000,依此類推,直到完成。
作為第一種方法,您可以嘗試調整一些JVM內存設置,例如,如建議的那樣增加堆大小,甚至使用AggressiveHeap選項。 考慮到大量文件,這可能沒有幫助,那么我建議解決問題。 創建幾個文件名,每個文件名為500k文件名,並從中讀取。
試試這個,它對我有用,但我沒有那么多文件......
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];
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.