ero*_*ppa 20 java filesystems performance file-io
假设一个非常简单的程序列出了给定目录的所有子目录.声音够简单?除了列出Java中所有子目录的唯一方法是使用FilenameFilter与File.list()结合使用.
这适用于简单的情况,但是当文件夹说出150,000个文件和2个子文件夹时,它在那里愚蠢地等待45秒,遍历所有文件并测试file.isDirectory().是否有更好的方法列出子目录?
PS.对不起,请保存有关在同一目录中包含太多文件的讲座.我们的现场环境将此作为要求的一部分.
Emi*_*l H 11
如已经提到的,这基本上是硬件问题.磁盘访问总是很慢,并且大多数文件系统并非真正设计用于处理具有那么多文件的目录.
如果由于某种原因必须将所有文件存储在同一目录中,我认为您必须维护自己的缓存.这可以使用本地数据库完成,例如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这样的网址 - 这意味着保持每个目录的文件数量很小.可以将其视为数据库中的BTree索引.
当然,您可以隐藏该结构:您可以创建一个文件系统抽象层,该层采用文件名并自动生成可以找到这些文件名的目录树.
如果这 15 万个文件(或其中很大一部分)都具有类似的命名约定,则您可以破解它,例如:
*.jpg
*Out.txt
并且实际上只为那些您不确定是否是文件夹的文件对象创建文件对象。
我不知道花费的开销是否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);
}
...
在枚举大量文件的 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<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;
}