使用Java计算目录中文件的数量

80

如何使用Java计算目录中的文件数?为了简单起见,我们假设该目录没有任何子目录。

我知道标准方法是:

new File(<directory path>).listFiles().length

但是这样做实际上会遍历目录中的所有文件,如果文件数量很大可能会花费很长时间。此外,除非文件数量超过某个固定的大数字(比如5000),否则我并不关心目录中的实际文件。

我猜测,目录(或Unix系统中的i-node)是否存储其中包含的文件数?如果我可以直接从文件系统获取该数字,那么效率将会更高。在Tomcat服务器的每个HTTP请求开始进行后端真正处理之前,都需要进行此检查。因此,速度至关重要。

我可以定期运行守护进程清除目录。我知道这一点,请不要给我这个解决方案。


如果目录可能有大量文件(1000个或更多),则您可能希望避免分配由File列表方法返回的数组。我还没有尝试过这个,但也许您可以使用listFiles并传递一个FileFilter实例,在accept方法中计算文件数量,同时对所有文件返回false。我认为这可以避免数组分配,同时仍然给您一个文件计数。 - Tom Fennelly
忽略我的最后一条评论...根据JDK的实现,该数组可能会在底层被分配(不管你是否调用)。至少在Openjdk中似乎是这种情况。 - Tom Fennelly
对于Java 7及更高版本,这个问题有一个标准的Java API很好地解决了。请参见@mateuscb在下面的答案 - https://dev59.com/yXRB5IYBdhLWcg3wNk93#30784016。 - Andy Thomas
12个回答

97
啊...Java没有一个简单的方法来做到这一点的原因是文件存储抽象化:有些文件系统可能没有目录中文件数量的信息可用...这个计数甚至可能根本没有任何意义(例如分布式,P2P文件系统,将文件列表存储为链表或基于数据库的文件系统...)。
所以,是的,
new File(<directory path>).list().length

这可能是您最好的选择。


1
在我看来,这并不能证明没有这样的方法是合理的 - 它可以简单地返回FS为N/A的null值。奇异的FS不是浪费时间获取数组的理由。 - Ondra Žižka
这对我来说没有意义。为什么你可以获取所有文件并计数,但不能简单地获取计数?有什么区别? - Jimmy T.
1
请注意,File.list() 可能会返回 null,例如当文件不是目录时。 - Bas Leijdekkers

46

自Java 8以来,您可以用三行代码实现该功能:

try (Stream<Path> files = Files.list(Paths.get("your/path/here"))) {
    long count = files.count();
}

关于5000个子节点和inode方面的问题:

这种方法将遍历条目,但正如Varkhan所建议的那样,除了使用JNI或直接系统命令调用外,您可能无法做得更好,但即使如此,您也永远无法确定这些方法是否做同样的事情!

然而,让我们深入一点:

查看JDK8源代码,Files.list公开了一个流,该流使用来自Files.newDirectoryStreamIterable,该迭代器委托给FileSystemProvider.newDirectoryStream

在UNIX系统上(反编译sun.nio.fs.UnixFileSystemProvider.class),它加载一个迭代器:使用sun.nio.fs.UnixSecureDirectoryStream(在迭代目录时带有文件锁)。

因此,在这里将循环遍历条目的迭代器。

现在,让我们看看计数机制。

实际计数是由Java 8 streams公开的count/sum reducing API执行的。理论上,这个API可以轻松执行并行操作(多线程)。但是,流是被创建为禁用并行处理,所以不可行...

这种方法的好处是,它不会在内存中加载数组,因为条目将由底层(Filesystem) API读取时,由迭代器计数。

最后,对于信息,概念上,在文件系统中,目录节点不需要保存其包含的文件数量,它只能包含其子节点的列表(即inode列表)。我不是一个文件系统方面的专家,但我相信UNIX文件系统就是这样工作的。因此,您不能直接获得这些信息(即:始终可能有某个隐藏的子节点列表)。


2
Java 8的Files.list()会抛出IOException异常;而File类的list()方法则不会抛出任何异常。 - prasad_
1
我一直在使用Files.list()来处理一个有100-200万个文件的目录,当然这需要一些时间。但我有一种感觉,这是我遇到的几个GC开销异常背后的原因,因为每次调用都会实例化和销毁数百万个文件对象。仍在寻找一种高效且内存安全的方法... - Antares42

17

很遗憾,我认为这已经是最好的方法了(尽管 list()listFiles() 稍微好一点,因为它不构造 File 对象)。


13

这可能不适用于你的应用程序,但你可以尝试使用本地调用(使用jni或jna),或执行特定于平台的命令并读取输出,然后再退回到list().length。在*nix上,你可以执行ls -1a | wc -l(注意-第一个命令是dash-one-a,第二个是小写L)。不确定在Windows上什么是正确的 - 可能只需要一个dir并查看摘要。

在尝试这样的事情之前,我强烈建议你创建一个包含大量文件的目录,并查看list().length是否真的需要太长时间。正如这位博主所建议的那样,你可能不想过度纠结于此。

我可能会选择Varkhan的答案。


1
ls 解决方案中使用 -a 是否合适?那样不会列出 ... 吗? - user212218
如果目录中有很多文件,我认为你可能需要一个 -f,否则大部分时间将花费在默认排序上。 - Glenn

8

由于您并不需要总数,实际上想在达到某个数字(在您的情况下是5000)后执行操作,因此可以使用java.nio.file.Files.newDirectoryStream。好处是您可以提前退出而不必浏览整个目录以获取计数。

public boolean isOverMax(){
    Path dir = Paths.get("C:/foo/bar");
    int i = 1;

    try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
        for (Path p : stream) {
            //larger than max files, exit
            if (++i > MAX_FILES) {
                return true;
            }
        }
    } catch (IOException ex) {
        ex.printStackTrace();
    }

    return false;
}

DirectoryStream 的接口文档也有一些很好的示例。


4
如果您有包含非常多(>100,000)文件的目录,这里有一种(不可移植的)解决方法:
String directoryPath = "a path";

// -f flag is important, because this way ls does not sort it output,
// which is way faster
String[] params = { "/bin/sh", "-c",
    "ls -f " + directoryPath + " | wc -l" };
Process process = Runtime.getRuntime().exec(params);
BufferedReader reader = new BufferedReader(new InputStreamReader(
    process.getInputStream()));
String fileCount = reader.readLine().trim() - 2; // accounting for .. and .
reader.close();
System.out.println(fileCount);

2
使用Sigar应该有所帮助。Sigar具有获取统计信息的本地钩子。
new Sigar().getDirStat(dir).getTotal()

我对你的回答抱有很高的期望,但是Sigar似乎已经无法使用了,还有其他的替代方案吗? - undefined

2
这种方法对我来说非常有效。
    // Recursive method to recover files and folders and to print the information
public static void listFiles(String directoryName) {

    File file = new File(directoryName);
    File[] fileList = file.listFiles(); // List files inside the main dir
    int j;
    String extension;
    String fileName;

    if (fileList != null) {
        for (int i = 0; i < fileList.length; i++) {
            extension = "";
            if (fileList[i].isFile()) {
                fileName = fileList[i].getName();

                if (fileName.lastIndexOf(".") != -1 && fileName.lastIndexOf(".") != 0) {
                    extension = fileName.substring(fileName.lastIndexOf(".") + 1);
                    System.out.println("THE " + fileName + "  has the extension =   " + extension);
                } else {
                    extension = "Unknown";
                    System.out.println("extension2 =    " + extension);
                }

                filesCount++;
                allStats.add(new FilePropBean(filesCount, fileList[i].getName(), fileList[i].length(), extension,
                        fileList[i].getParent()));
            } else if (fileList[i].isDirectory()) {
                filesCount++;
                extension = "";
                allStats.add(new FilePropBean(filesCount, fileList[i].getName(), fileList[i].length(), extension,
                        fileList[i].getParent()));
                listFiles(String.valueOf(fileList[i]));
            }
        }
    }
}

1

不幸的是,正如mmyers所说,使用Java时File.list()已经是最快的了。如果速度像你所说的那样重要,你可能需要考虑使用JNI来执行这个特定的操作。然后你可以根据你的特定情况和文件系统来定制你的代码。


1

统计目录及其所有子目录中的文件数。

var path = Path.of("your/path/here");
var count = Files.walk(path).filter(Files::isRegularFile).count();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接