【问题标题】:Counting the number of files in a directory using Java使用Java计算目录中的文件数
【发布时间】:2010-10-15 19:19:32
【问题描述】:

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

我知道的标准方法:

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

但这将有效地遍历目录中的所有文件,如果文件数量很大,这可能需要很长时间。另外,我不关心目录中的实际文件,除非它们的数量大于某个固定的大数字(比如 5000)。

我猜,但是目录(或者在 Unix 的情况下是它的 i 节点)不是存储其中包含的文件数吗?如果我可以直接从文件系统中获取该数字,它会快得多。在后端开始进行实际处理之前,我需要对 Tomcat 服务器上的每个 HTTP 请求进行此检查。因此,速度至关重要。

我可以每隔一段时间运行一个守护进程来清除目录。我知道,所以请不要给我那个解决方案。

【问题讨论】:

  • 如果目录可能有大量文件(1000s+),那么您可能希望避免分配文件列表方法返回的数组。我还没有尝试过,但也许你可以使用 listFiles 并传递一个 FileFilter 实例,该实例在 accept 方法中对文件进行计数,同时为所有文件返回 false。我假设这避免了数组分配,同时仍然给你一个文件计数。
  • 忽略我的最后评论...根据 JDK impl,无论如何都可能分配数组(在后台)。无论如何,在openjdk中似乎就是这种情况。
  • 对于 Java 7 及更高版本,使用标准 Java API 可以很好地解决此问题。请参阅下面@mateuscb 的答案 - stackoverflow.com/questions/687444/…。

标签: java performance file directory


【解决方案1】:

啊...在 Java 中没有直接的方法来执行此操作的基本原理是文件存储抽象:某些文件系统可能没有目录中现成可用的文件数量...该计数甚至可能没有任何意义完全没有(参见例如分布式、P2P 文件系统、将文件列表存储为链表的 fs 或数据库支持的文件系统......)。 所以是的,

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

可能是你最好的选择。

【讨论】:

  • IMO,这并不能证明没有这种方法是合理的 - 它可以简单地为 N/A 的 FS 返回 null。异国情调的 FS 不是浪费周期来获取数组的理由。
  • 这对我来说没有意义。为什么您可以获取所有文件并对其进行计数,而不是简单地获取计数?区别在哪里?
  • 注意File.list() 可以返回null,例如当文件不是目录时。
【解决方案2】:

从 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.newDirectoryStream 的 Iterable 委托给 FileSystemProvider.newDirectoryStream。

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

所以,有一个迭代器会遍历这里的条目。

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

实际计数由Java 8 streams 公开的计数/和减少API 执行。理论上,这个 API 可以毫不费力地执行并行操作(使用多线程)。但是,流是在禁用并行性的情况下创建的,所以这是不行的......

这种方法的好的一面是它不会将数组加载到内存中,因为条目将由迭代器计算,因为它们被底层读取(文件系统)API。

最后,对于信息,从概念上讲,在文件系统中,目录节点不需要保存它包含的文件的数量,它可以只包含它的子节点列表(inode 列表)。我不是文件系统方面的专家,但我相信 UNIX 文件系统就是这样工作的。所以你不能假设有一种方法可以直接获取这些信息(即:总是可以在某处隐藏一些子节点列表)。

【讨论】:

  • Java 8 Files.list() 抛出 IOException; File 类的 list() 方法不会抛出任何异常。
  • 我一直使用Files.list() 来处理包含 1-2 百万个文件的目录,当然这需要一段时间。但我有一种感觉,这是我遇到的一些 GC 开销异常的原因,因为每次调用都会实例化和销毁数百万个文件对象。仍在寻找一种高性能且内存安全的方法...
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    在处理这样的事情之前,我强烈建议您创建一个包含大量文件的目录,然后看看 list().length 是否真的需要太长时间。正如this blogger 建议的那样,您可能不想为此出汗。

    我自己可能会接受 Varkhan 的回答。

    【讨论】:

    • -a 适用于ls 解决方案吗?那不也列出.和..吗?
    • 如果目录中有很多文件,我认为您可能想要-f,否则大部分时间将花在进行默认排序上。
    【解决方案5】:

    由于您实际上并不需要总数,并且实际上想在某个数字(在您的情况下为 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 的interface doc 也有一些很好的例子。

    【讨论】:

      【解决方案6】:

      如果你的目录包含很多 (>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);
      

      【讨论】:

        【解决方案7】:

        使用 sigar 应该会有所帮助。 Sigar 具有获取统计信息的原生钩子

        new Sigar().getDirStat(dir).getTotal()
        

        【讨论】:

          【解决方案8】:

          这个方法对我很有效。

              // 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]));
                      }
                  }
              }
          }
          

          【讨论】:

            【解决方案9】:

            不幸的是,正如 mmyers 所说,File.list() 的速度与您使用 Java 的速度差不多。如果速度和您说的一样重要,您可能需要考虑使用JNI 执行此特定操作。然后,您可以根据您的特定情况和文件系统定制您的代码。

            【讨论】:

              【解决方案10】:
              public void shouldGetTotalFilesCount() {
                  Integer reduce = of(listRoots()).parallel().map(this::getFilesCount).reduce(0, ((a, b) -> a + b));
              }
              
              private int getFilesCount(File directory) {
                  File[] files = directory.listFiles();
                  return Objects.isNull(files) ? 1 : Stream.of(files)
                          .parallel()
                          .reduce(0, (Integer acc, File p) -> acc + getFilesCount(p), (a, b) -> a + b);
              }
              

              【讨论】:

                【解决方案11】:

                统计目录和所有子目录中的文件。

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

                【讨论】:

                  【解决方案12】:

                  在春季批次中我做了以下

                  private int getFilesCount() throws IOException {
                          ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
                          Resource[] resources = resolver.getResources("file:" + projectFilesFolder + "/**/input/splitFolder/*.csv");
                          return resources.length;
                      }
                  

                  【讨论】:

                    猜你喜欢
                    • 2010-11-10
                    • 2016-02-19
                    • 2011-02-07
                    • 2011-10-15
                    • 2019-01-16
                    • 2015-06-28
                    • 2021-08-25
                    • 2011-12-24
                    • 1970-01-01
                    相关资源
                    最近更新 更多