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

如何在Java中快速检索目录列表?,java,performance,file-io,filesystems,Java,Performance,File Io,Filesystems,假设有一个非常简单的程序,列出给定目录的所有子目录。听起来够简单吗?除了在Java中列出所有子目录的唯一方法是使用组合 这适用于一般情况,但当文件夹包含150000个文件和2个子文件夹时,在那里等待45秒迭代所有文件并测试file.isDirectory()是愚蠢的。有没有更好的方法列出子目录 另外,抱歉,请保存关于同一目录中文件过多的讲座。我们的实时环境将此作为要求的一部分。如果所有150k文件(或相当数量的文件)都有类似的命名约定,您可以对其进行破解,如: *.jpg *Out.txt

假设有一个非常简单的程序,列出给定目录的所有子目录。听起来够简单吗?除了在Java中列出所有子目录的唯一方法是使用组合

这适用于一般情况,但当文件夹包含150000个文件和2个子文件夹时,在那里等待45秒迭代所有文件并测试file.isDirectory()是愚蠢的。有没有更好的方法列出子目录



另外,抱歉,请保存关于同一目录中文件过多的讲座。我们的实时环境将此作为要求的一部分。

如果所有150k文件(或相当数量的文件)都有类似的命名约定,您可以对其进行破解,如:

*.jpg
*Out.txt

并且只为那些您不确定是否为文件夹的对象创建文件对象。

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

在这种情况下,您可能会尝试一些JNA解决方案—一种依赖于平台的目录遍历器(Windows上的FindFirst、FindNext),并可能采用某种迭代模式。另外,Java7将有更好的文件系统支持,值得查看规范(我不记得任何细节)


编辑:一个想法:一个选择是隐藏目录列表的缓慢性,不让用户看到。在客户端应用程序中,您可以在列表工作时使用一些动画来分散用户的注意力。实际上,这取决于你的应用程序在列表之外还做了什么。

你得到讲座的原因其实是:它是你问题的正确答案。这是背景,也许你可以在你的生活环境中做一些改变

第一:目录存储在文件系统上;把它们想象成文件,因为它们就是这样的。当您遍历目录时,必须从磁盘读取这些块。每个目录条目都需要足够的空间来保存文件名、权限以及该文件在磁盘上的位置信息

第二:目录不是以任何内部顺序存储的(至少在我处理过目录文件的文件系统中不是这样)。如果您有150000个条目和2个子目录,那么这2个子目录引用可能在150000个条目中的任何位置。你必须迭代才能找到它们,这是没有办法的

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

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


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

好吧,JNI,或者,如果您说您的部署是恒定的,只需在Windows上运行“dir”或在*nixes上运行“ls”,使用适当的标志只列出目录(Runtime.exec())

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

否则,在大多数底层OSs中,您无法仅获取目录名(例如,在Unix中,目录列表只是读取“目录”文件的内容,因此如果不列出所有文件,就无法快速找到“仅目录”)


然而,在Java7中的NIO.2中(请参阅),有一种方法可以拥有一个流目录列表,这样您就不会在内存/网络中得到一个完整的文件元素数组。

我不知道向
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表示从根目录返回完整路径名

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

  • 在UNIX上
  • 以及Windows上的相关API
  • Java7与NIO2

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

正如前面提到的,这基本上是一个硬件问题。磁盘访问总是很慢,而且大多数文件系统并不是专门为处理包含那么多文件的目录而设计的

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

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

images/[id - (id % 1000000)]/[id - (id % 1000)]/[id].jpg
这对我们来说很有效,这是我推荐的解决方案。你可以做一些类似于字母数字文件名的事情,只需取第一个t
/symlinks/a/b/cde
/realfiles/abcde
for (File f : new File("C:\\").listFiles()) {
    if (f.isDirectory()) {
        continue;
    }        
}
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<File> dir_listFiles() {
    List<File> 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> path_walkTree() throws Exception {
    final List<Path> files = new ArrayList<>(1000);

    for( int i = 0; i < nCycles; i++ ) {
        Path dir = Paths.get(testDir);

        files.clear();
        Files.walkFileTree(dir, new SimpleFileVisitor<Path> () {
            @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> path_find() throws Exception {
    final List<Path> 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;
}