簡體   English   中英

如何在java中列出200萬個文件目錄,而不會出現“內存不足”異常

[英]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? 你有一個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))

如果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//adbshel​​l.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM