如何在java中列出200万个文件目录而没有“内存不足”exception

我必须处理一个大约200万xml的目录进行处理。

我已经解决了使用队列在机器和线程之间分配工作的处理,一切正常。

但现在最大的问题是用200万个文件读取目录的瓶颈,以便逐步填充队列。

我尝试过使用File.listFiles()方法,但它给了我一个java out of memory: heap spaceexception。 有任何想法吗?

首先,你有可能使用Java 7吗? 你有一个FileVisitorFiles.walkFileTree ,它应该可以在你的内存限制内工作。

否则,我能想到的唯一方法是使用File.listFiles(FileFilter filter)和一个总是返回false的filter(确保完整的文件数组永远不会保存在内存中),但是它会捕获要处理的文件方式,也许可以将它们放在生产者/消费者队列中,或者将文件名写入磁盘以供以后遍历。

或者,如果您控制文件的名称,或者它们以某种不错的方式命名,您可以使用在文件file0000000上接受文件名的filter来处理文件块 – filefile0001000然后是file0001000filefile0002000 ,依此类推。

如果名称没有以这样的好方式命名,您可以尝试根据文件名的哈希码来过滤它们,该哈希码应该在整数集上相当均匀地分布。


更新:叹息。 可能不起作用。 刚看了一下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() { @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 it = FileUtils.iterateFiles(folder, null, true); while (it.hasNext()) { File fileEntry = (File) it.next(); } 

希望有所帮助。 再见

由于你在Windows上,似乎你应该只使用ProcessBuilder来启动类似“cmd / k dir / b target_directory”的东西,捕获它的输出,并将其路由到文件中。 然后,您可以一次处理该文件,读取文件名并处理它们。

迟到总比不到好? ;)

首先,您可以尝试通过-Xmx1024m来增加JVM的内存,例如

为什么要在同一目录中存储200万个文件呢? 我可以想象它已经在操作系统级别上严重降低了访问速度。

我肯定希望在处理之前将它们分成子目录(例如,按创建的日期/时间)。 但如果由于某种原因不可能,可以在处理过程中完成吗? 例如,将排队等待Process1的1000个文件移动到Directory1中,将Process2的另外1000个文件移动到Directory2等等。然后每个进程/线程只看到为其分配的(有限数量的)文件。

请发布OOMexception的完整堆栈跟踪以确定瓶颈的位置,以及显示您看到的行为的简短,完整的Java程序。

这很可能是因为你收集了内存中的所有200万个条目,并且它们不合适。 你能增加堆空间吗?

如果文件名遵循某些规则,则可以使用File.list(filter)而不是File.listFiles来获取文件列表的可管理部分。

作为第一种方法,您可以尝试调整一些JVM内存设置,例如,如建议的那样增加堆大小,甚至使用AggressiveHeap选项。 考虑到大量文件,这可能没有帮助,那么我建议解决问题。 创建几个文件中包含文件名的文件,比如每个文件500k文件名并从中读取。

我开发恶意软件扫描应用程序时遇到了同样的问题。 我的解决方案是执行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. 

这也需要Java 7,但如果您只想列出目录的内容而不是遍历整个树,它比Files.walkFileTree答案更简单:

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

DirectoryStream的实现是特定于平台的,并且从不调用File.list或类似的东西,而是使用Unix或Windows系统调用,一次迭代一个目录。

您可以将listFiles与特殊的FilenameFilter一起使用。 第一次将FilenameFilter发送到listFiles时,它接受前1000个文件,然后将它们保存为访问过的文件。

下次将FilenameFilter发送到listFiles时,它会忽略前1000个访问文件并返回下一个1000,依此类推,直到完成。

试试这个,它对我有用,但我没有那么多文件……

 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