如何在java中列出200万个文件目录而没有“内存不足”exception
我必须处理一个大约200万xml的目录进行处理。
我已经解决了使用队列在机器和线程之间分配工作的处理,一切正常。
但现在最大的问题是用200万个文件读取目录的瓶颈,以便逐步填充队列。
我尝试过使用File.listFiles()
方法,但它给了我一个java out of memory: heap space
exception。 有任何想法吗?
首先,你有可能使用Java 7吗? 你有一个FileVisitor
和Files.walkFileTree
,它应该可以在你的内存限制内工作。
否则,我能想到的唯一方法是使用 File.listFiles(FileFilter filter)
和一个总是返回false
的filter(确保完整的文件数组永远不会保存在内存中),但是它会捕获要处理的文件方式,也许可以将它们放在生产者/消费者队列中,或者将文件名写入磁盘以供以后遍历。
或者,如果您控制文件的名称,或者它们以某种不错的方式命名,您可以使用在文件file0000000
上接受文件名的filter来处理文件块 – 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() { @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 : //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 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