如何在Java中快速检索目录列表?

假设一个非常简单的程序列出了给定目录的所有子目录。 声音够简单? 除了列出Java中所有子目录的唯一方法是使用FilenameFilter与File.list()结合使用。

这适用于琐碎的情况,但是当文件夹说出150,000个文件和2个子文件夹时,它在那里愚蠢地等待45秒,遍历所有文件并测试file.isDirectory()。 是否有更好的方法列出子目录?


PS。 对不起,请保存有关在同一目录中包含太多文件的讲座。 我们的现场环境将此作为要求的一部分。

如已经提到的,这基本上是硬件问题。 磁盘访问总是很慢,并且大多数文件系统并非真正设计用于处理具有那么多文件的目录。

如果由于某种原因必须将所有文件存储在同一目录中,我认为您必须维护自己的缓存。 这可以使用本地数据库完成,例如sqlite,HeidiSQL或HSQL。 如果您想要极致性能,请使用java TreeSet并将其缓存在内存中。 这意味着至少你必须不经常阅读目录,它可能在后台完成。 通过使用系统本机文件更新通知API(Linux上的inotify)来订阅对目录的更改,您可以进一步减少刷新列表的需要。

这对您来说似乎不太可能,但我曾经通过将文件“散列”到子目录中来解决类似的问题。 就我而言,挑战在于使用数字ID存储数百万个图像。 我构建了如下目录结构:

images/[id - (id % 1000000)]/[id - (id % 1000)]/[id].jpg 

这对我们来说效果很好,这是我推荐的解决方案。 你可以通过简单地取文件名的前两个字母然后接下来的两个字母来做类似于字母数字文件名的操作。 我也做过一次这样做,它也完成了这项工作。

你知道可能的子目录名称的有限列表吗? 如果是这样,请对所有可能的名称使用循环并检查目录是否存在。

否则,你无法获得大多数底层操作系统中的目录名称(例如在Unix中,目录列表只是读取“目录”文件的内容,所以没有列出所有文件就无法快速找到“只是目录”)。

但是,在Java7中的NIO.2中(参见http://java.sun.com/developer/technicalArticles/javase/nio/#3 ),有一种方法可以获得流式目录列表,这样就不会得到完整的数组文件元素混乱你的内存/网络。

实际上有一个原因让你得到讲座:这是你问题的正确答案。 这是背景,因此您可以在现场环境中进行一些更改。

第一:目录存储在文件系统中; 将它们视为文件,因为它们正是它们的本质。 当您遍历目录时,您必须从磁盘中读取这些块。 每个目录条目都需要足够的空间来保存文件名,权限以及有关在磁盘上找到该文件的位置的信息。

第二:目录不存储任何内部排序(至少,不在我使用目录文件的文件系统中)。 如果您有150,000个条目和2个子目录,那么这2个子目录引用可以在150,000之内的任何位置。 你必须迭代才能找到它们,没有办法解决这个问题。

所以,让我们说你无法避免大目录。 您唯一真正的选择是尝试将包含目录文件的块保留在内存缓存中,这样您每次访问它时都不会访问磁盘。 您可以通过在后台线程中定期迭代目录来实现此目的 – 但这会导致磁盘上的过度负载,并干扰其他进程。 或者,您可以扫描一次并跟踪结果。

另一种方法是创建分层目录结构。 如果你查看商业网站,你会看到像/1/150/15023.html这样的url – 这意味着保持每个目录的文件数量很小。 将其视为数据库中的BTree索引。

当然,您可以隐藏该结构:您可以创建一个文件系统抽象层,该层采用文件名并自动生成可以找到这些文件名的目录树。

我不知道炮轰cmd.exe的开销会不会消耗它,但有一种可能性是这样的:

 ... Runtime r = Runtime.getRuntime(); Process p = r.exec("cmd.exe /k dir /s/b/ad C:\\folder"); BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); for (;;) { String d = br.readLine(); if (d == null) break; System.out.println(d); } ... 
  • / s表示搜索子目录
  • / ad表示只返回目录
  • / b表示从根返回完整路径名

如果150k文件全部(或其中很大一部分)具有类似的命名约定,你可以破解它:

 *.jpg *Out.txt 

并且实际上只为那些你不确定是文件夹的文件创建文件对象。

关键问题可能是循环中调用的File.isDirectory()函数。

File.isDirectory()可能非常慢。 我看到NFS花了10秒钟来处理200个文件目录。

如果你可以通过各种方式阻止File.isDirectory()调用(例如测试扩展,没有扩展名==目录),你可以大大提高性能。

否则我建议你做JNA / JNI /写一个为你做这个的本机脚本。

jCifs库使您可以更有效地操作Windows网络共享。 我不知道有一个库可以为其他网络文件系统执行此操作。

如果您的操作系统“稳定”,请尝试JNA :

  • UNIX上的opendir / readdir
  • Windows上的FindFirstFile和相关API
  • 带有NIO2的Java7

这些都是“流API”。 在开始搜索之前,它们不会强制您分配150k列表/数组。 恕我直言,这在你的场景中是一个很大的优势。

这是一个离线解决方案,完全没有任何测试。 它还依赖于拥有支持符号链接的文件系统。 这不是Java解决方案。 我怀疑你的问题是文件系统/操作系统相关,而不是Java相关。

是否可以创建并行目录结构,子目录基于文件名的首字母,然后符号链接到真实文件? 一个例证

 /symlinks/a/b/cde 

将链接到

 /realfiles/abcde 

(其中/ realfiles是您的150,000个文件所在的位置)

你必须创建和维护这个目录结构,我没有足够的信息来确定这是否实用。 但是上面会为你的非分层(和慢)目录创建一个快速(呃)索引。

在http://blogs.oracle.com/adventures/entry/fast_directory_scanning上还有一个递归并行扫描。 基本上兄弟姐妹是并行处理的。 还有令人鼓舞的性能测试。

在枚举大量文件的Java应用程序中调试性能时,我遇到了类似的问题。 它使用旧方法

 for (File f : new File("C:\\").listFiles()) { if (f.isDirectory()) { continue; } } 

并且似乎每个f.isDirectory()都是对本机FileSsystem的调用,至少在NTFS上它是非常慢的。 Java7 NIO有额外的API,但并非所有方法都很好。 我将在这里提供JMH基准测试结果

 Benchmark Mode Cnt Score Error Units MyBenchmark.dir_listFiles avgt 5 0.437 ? 0.064 s/op MyBenchmark.path_find avgt 5 0.046 ? 0.001 s/op MyBenchmark.path_walkTree avgt 5 1.702 ? 0.047 s/op 

编号来自执行此代码:

 java -jar target/benchmarks.jar -bm avgt -f 1 -wi 5 -i 5 -t 1 static final String testDir = "C:/Sdk/Ide/NetBeans/src/dev/src/"; static final int nCycles = 50; public static class Counter { int countOfFiles; int countOfFolders; } @Benchmark public List dir_listFiles() { List files = new ArrayList<>(1000); for( int i = 0; i < nCycles; i++ ) { File dir = new File(testDir); files.clear(); for (File f : dir.listFiles()) { if (f.isDirectory()) { continue; } files.add(f); } } return files; } @Benchmark public List path_walkTree() throws Exception { final List files = new ArrayList<>(1000); for( int i = 0; i < nCycles; i++ ) { Path dir = Paths.get(testDir); files.clear(); Files.walkFileTree(dir, new SimpleFileVisitor () { @Override public FileVisitResult visitFile(Path path, BasicFileAttributes arg1) throws IOException { files.add(path); return FileVisitResult.CONTINUE; } @Override public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes arg1) throws IOException { return path == dir ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE; } }); } return files; } @Benchmark public List path_find() throws Exception { final List files = new ArrayList<>(1000); for( int i = 0; i < nCycles; i++ ) { Path dir = Paths.get(testDir); files.clear(); files.addAll(Files.find(dir, 1, (path, attrs) -> true /*!attrs.isDirectory()*/).collect(Collectors.toList())); } return files; } 

也许您可以用C#/ C / C ++编写目录搜索程序,并使用JNI将其转换为Java。 不知道这是否会提高性能。

好吧,无论是JNI,还是,如果你说你的部署是不变的,只需在Windows上运行“dir”或在* nixes上运行“ls”,使用适当的标志仅列出目录(Runtime.exec())

在这种情况下,您可以尝试一些JNA解决方案 – 平台依赖的目录遍历器(Windows上的FindFirst,FindNext),可能存在一些迭代模式。 此外,Java 7将拥有更好的文件系统支持,值得查看规范(我不记得任何细节)。

编辑:一个想法:一个选项是从用户的眼睛隐藏目录列表的缓慢。 在客户端应用程序中,您可以使用一些动画,而列表正在努力分散用户的注意力。 实际上取决于您的应用程序在列表旁边做了什么。