使用Java递归列出目录中的所有文件


85

我具有此功能,该功能以递归方式打印目录中所有文件的名称。问题是我的代码很慢,因为它每次迭代都必须访问远程网络设备。

我的计划是先递归地从目录中加载所有文件,然后再使用正则表达式遍历所有文件以过滤掉所有我不需要的文件。有谁有更好的建议?

public static printFnames(String sDir){
  File[] faFiles = new File(sDir).listFiles();
  for(File file: faFiles){
    if(file.getName().matches("^(.*?)")){
      System.out.println(file.getAbsolutePath());
    }
    if(file.isDirectory()){
      printFnames(file.getAbsolutePath());
    }
  }
}

这只是稍后的测试,我将不使用这样的代码,而是将与高级正则表达式匹配的每个文件的路径和修改日期添加到数组中。


1
... 问题是什么?您是否只是在寻找验证此代码是否有效?
理查德JP Le Guen 2010年

不,我知道这段代码可以工作,但是它非常慢,而且感觉像愚蠢地访问文件系统并获取每个子目录的内容,而不是一次获取所有内容。
霍特纳

Answers:


134

假设这是您要编写的实际生产代码,那么我建议对已经解决的此类问题使用解决方案,特别是Apache Commons IOFileUtils.listFiles()。它处理嵌套目录,过滤器(基于名称,修改时间等)。

例如,对于您的正则表达式:

Collection files = FileUtils.listFiles(
  dir, 
  new RegexFileFilter("^(.*?)"), 
  DirectoryFileFilter.DIRECTORY
);

这将递归搜索与^(.*?)正则表达式匹配的文件,并以集合的形式返回结果。

值得注意的是,这不会比滚动自己的代码快,它的作用相同-用Java拖曳文件系统只是很慢。所不同的是,Apache Commons版本将没有错误。


我到那里看了,然后我将使用commons.apache.org/io/api-release/index.html?org/apache/commons/…从目录和子目录中获取所有文件,然后搜索这些文件,以便他们匹配我的正则表达式。还是我错了?
霍尔特纳

是的问题是,扫描文件夹需要一个多小时,而每次我启动程序以检查更新时,这样做都是非常烦人的。如果我用C编写程序的这一部分,然后用Java编写其余的程序,会更快吗?现在,我更改了if isdir行上的代码并添加了代码,以便目录还必须匹配要包含在搜索中的正则表达式。我在您的示例中看到它说DirectoryFileFilter.DIRECTORY,我想我在那里可以有一个正则表达式过滤器。
霍特纳

1
使用本机调用编写它绝对可以使其更快-FindFirstFile / FineNextFile允许您查询文件属性,而无需对其进行单独调用-这可能会对更高延迟的网络产生重大影响。Java的处理方法效率极低。
凯文·戴

5
@ hanzallah-afgan:问题和答案都超过5年了。这段时间里有两个主要的Java版本,因此您可能不希望研究Java 7 NIO等较新的功能。
Hultner

4
仅当您了解并接受性能影响时,才使用FileUtilsgithub.com/brettryan/io-recurse-tests。本机java8替代方法允许使用简洁高效的符号,例如:Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList())
ccpizza

64

在Java 8,它是通过1衬垫Files.find()与一个任意大的深度(例如999)和BasicFileAttributesisRegularFile()

public static printFnames(String sDir) {
    Files.find(Paths.get(sDir), 999, (p, bfa) -> bfa.isRegularFile()).forEach(System.out::println);
}

要添加更多过滤条件,请增强lambda,例如最近24小时内修改的所有jpg文件:

(p, bfa) -> bfa.isRegularFile()
  && p.getFileName().toString().matches(".*\\.jpg")
  && bfa.lastModifiedTime().toMillis() > System.currentMillis() - 86400000

3
我建议始终使用那些在try-with-resources块中返回Stream的Files方法:否则,您将保持资源打开状态
riccardo.tasso

终端操作本身不是在流上调用close吗?
德拉加斯

@Dragas是的。我的消费者只是一个简单的例子;在现实生活中,您会做些更有用的事情。
波西米亚

27

这是从给定根目录获取所有文件的非常简单的递归方法。

它使用Java 7 NIO Path类。

private List<String> getFileNames(List<String> fileNames, Path dir) {
    try(DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
        for (Path path : stream) {
            if(path.toFile().isDirectory()) {
                getFileNames(fileNames, path);
            } else {
                fileNames.add(path.toAbsolutePath().toString());
                System.out.println(path.getFileName());
            }
        }
    } catch(IOException e) {
        e.printStackTrace();
    }
    return fileNames;
} 

18

使用Java 7,通过PathsFiles功能引入了一种更快的遍历目录树的方法。它们比“旧”File方法快得多。

这将是使用正则表达式遍历并检查路径名的代码:

public final void test() throws IOException, InterruptedException {
    final Path rootDir = Paths.get("path to your directory where the walk starts");

    // Walk thru mainDir directory
    Files.walkFileTree(rootDir, new FileVisitor<Path>() {
        // First (minor) speed up. Compile regular expression pattern only one time.
        private Pattern pattern = Pattern.compile("^(.*?)");

        @Override
        public FileVisitResult preVisitDirectory(Path path,
                BasicFileAttributes atts) throws IOException {

            boolean matches = pattern.matcher(path.toString()).matches();

            // TODO: Put here your business logic when matches equals true/false

            return (matches)? FileVisitResult.CONTINUE:FileVisitResult.SKIP_SUBTREE;
        }

        @Override
        public FileVisitResult visitFile(Path path, BasicFileAttributes mainAtts)
                throws IOException {

            boolean matches = pattern.matcher(path.toString()).matches();

            // TODO: Put here your business logic when matches equals true/false

            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path path,
                IOException exc) throws IOException {
            // TODO Auto-generated method stub
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path path, IOException exc)
                throws IOException {
            exc.printStackTrace();

            // If the root directory has failed it makes no sense to continue
            return path.equals(rootDir)? FileVisitResult.TERMINATE:FileVisitResult.CONTINUE;
        }
    });
}

5
好的答案:),还有一个名为“ SimpleFileVisitor”的实现类,如果不需要所有实现的功能,则可以重写所需的功能。
GalDude33 2014年

13

使用Java 7 NIO获取目录内容的快速方法:

import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.FileSystems;
import java.nio.file.Path;

...

Path dir = FileSystems.getDefault().getPath( filePath );
DirectoryStream<Path> stream = Files.newDirectoryStream( dir );
for (Path path : stream) {
   System.out.println( path.getFileName() );
}
stream.close();

3
很好,但仅获取一个目录的文件。如果要查看所有子目录,请参阅我的替代答案。
2014年

3
Files.newDirectoryStream可以抛出IOException。我建议将该行包装在Java7 try-with-statement中,以使流始终为您关闭(是否为异常,而无需finally)。另请参阅此处:stackoverflow.com/questions/17739362/…–
Greg

12

Java的读取文件系统文件夹内容的接口不是很有效(如您所见)。JDK 7为这种事情提供了一个全新的界面来解决此问题,它将为这些操作带来本机级性能。

核心问题是Java对每个文件都进行本地系统调用。在低延迟的接口上,这没什么大不了的,但是在延迟中等的网络上,它的确加起来了。如果您在上面介绍算法,则会发现大部分时间都花在讨厌的isDirectory()调用上-这是因为每次对isDirectory()的调用都需要往返。最初请求文件/文件夹列表时,大多数现代OS都可以提供此类信息(与查询每个文件路径的属性相反)。

如果您不能等待JDK7,解决此延迟的一种策略是使用多线程,并使用具有最大线程数的ExecutorService来执行递归。这不是很好(您必须处理输出数据结构的锁定),但是比执行此单线程要快得多。

在有关此类问题的所有讨论中,我强烈建议您与使用本机代码(甚至是执行大致相同操作的命令行脚本)可以做的最好的事情进行比较。说遍历一个网络结构需要一个小时,实际上并不意味着那么多。告诉我们您可以在7秒钟内完成本机操作,但是用Java花费一个小时将引起人们的注意。


3
现在有Java 7,因此有关如何在Java 7中执行此操作的示例将很有帮助。或至少一个链接。或要在Google上搜索的班级名称。—毕竟是«stackoverflow»,而不是«theory cs»;-)。
马丁

3
好吧,让我看看...我的原始帖子是在2010年3月...现在是2012年1月...而我只是查看了我的设备库存历史记录,而我看不到自己在10年3月拥有一台时光机,所以我认为我在没有给出明确示例的情况下回答是有道理的;-)
Kevin Day


7

这将正常工作...及其递归

File root = new File("ROOT PATH");
for ( File file : root.listFiles())
{
    getFilesRecursive(file);
}


private static void getFilesRecursive(File pFile)
{
    for(File files : pFile.listFiles())
    {
        if(files.isDirectory())
        {
            getFilesRecursive(files);
        }
        else
        {
            // do your thing 
            // you can either save in HashMap and use it as
            // per your requirement
        }
    }
}

1
如果您想要与Java <7兼容的东西,那就很好的答案。
ssimm '16

3

我个人喜欢这个版本的FileUtils。这是一个示例,可在目录或其任何子目录中查找所有mp3或flac:

String[] types = {"mp3", "flac"};
Collection<File> files2 = FileUtils.listFiles(/path/to/your/dir, types , true);

3

这样就可以了

public void displayAll(File path){      
    if(path.isFile()){
        System.out.println(path.getName());
    }else{
        System.out.println(path.getName());         
        File files[] = path.listFiles();
        for(File dirOrFile: files){
            displayAll(dirOrFile);
        }
    }
}


欢迎使用StackOverflow Mam,您能否说明您的答案是对许多现有答案的改进还是替代?
Lilienthal 2015年

1

该功能可能会列出目录及其子目录中的所有文件名及其路径。

public void listFile(String pathname) {
    File f = new File(pathname);
    File[] listfiles = f.listFiles();
    for (int i = 0; i < listfiles.length; i++) {
        if (listfiles[i].isDirectory()) {
            File[] internalFile = listfiles[i].listFiles();
            for (int j = 0; j < internalFile.length; j++) {
                System.out.println(internalFile[j]);
                if (internalFile[j].isDirectory()) {
                    String name = internalFile[j].getAbsolutePath();
                    listFile(name);
                }

            }
        } else {
            System.out.println(listfiles[i]);
        }

    }

}

1
此示例未考虑listFiles()方法可以并且将返回null的事实。docs.oracle.com/javase/7/docs/api/java/io/File.html#listFiles()
马特·琼斯

1

Java 8

public static void main(String[] args) throws IOException {

        Path start = Paths.get("C:\\data\\");
        try (Stream<Path> stream = Files.walk(start, Integer.MAX_VALUE)) {
            List<String> collect = stream
                .map(String::valueOf)
                .sorted()
                .collect(Collectors.toList());

            collect.forEach(System.out::println);
        }


    }

0

感觉就像愚蠢地访问文件系统并获取每个子目录的内容,而不是一次获取所有内容。

你的感觉是错误的。这就是文件系统的工作方式。没有更快的方法(除非您必须重复执行此操作或针对不同的模式,可以将所有文件路径缓存在内存中,但是随后必须处理缓存失效,即在添加/删除/重命名文件时会发生什么情况。该应用程序运行)。


我想将具有特定名称格式的所有类型的所有文件加载到提供给用户的库中,并且每次启动该应用程序时,该库都应该进行更新,但是要花很长时间才能更新该库。我得到的唯一解决方案是在后台运行更新,但仍然令人讨厌的是,要花很长时间才能加载所有新文件。必须有更好的方法来做到这一点。或者至少是更新数据库的更好方法。遍历已经遍历的所有文件感觉很愚蠢。有没有一种方法只能快速查找更新。
Hultner

@Hultner:Java 7将包括一个用于通知文件系统更新的工具,但这仍然仅在应用程序运行时起作用,因此,除非您希望一直运行后台服务,否则将无济于事。正如Kevin所描述的,网络共享可能存在一些特殊问题,但是,只要您依靠扫描整个目录树,就没有更好的方法了。
Michael Borgwardt 2010年

也许您可以创建一些索引文件。如果有一种检查目录大小的方法,您可以在大小更改时简单地扫描新文件。
James P.

@James:无法检查目录大小。在我知道的所有文件系统中,目录的大小是通过获取每个文件的大小并将其相加而获得的。实际上,问题“此目录的大小是多少?” 如果您考虑硬链接,甚至根本没有任何意义。
Michael Borgwardt

你是对的。我仍然认为某些缓存和/或指纹识别可以加快该过程。
James P.


0

我发现处理数百万个文件夹和文件时更有效的方法是通过DOS命令在某些文件中捕获目录列表并进行解析。解析数据后,就可以进行分析和统计。


0
import java.io.*;

public class MultiFolderReading {

public void checkNoOfFiles (String filename) throws IOException {

    File dir=new File(filename);
    File files[]=dir.listFiles();//files array stores the list of files

 for(int i=0;i<files.length;i++)
    {
        if(files[i].isFile()) //check whether files[i] is file or directory
        {
            System.out.println("File::"+files[i].getName());
            System.out.println();

        }
        else if(files[i].isDirectory())
        {
            System.out.println("Directory::"+files[i].getName());
            System.out.println();
            checkNoOfFiles(files[i].getAbsolutePath());
        }
    }
}

public static void main(String[] args) throws IOException {

    MultiFolderReading mf=new MultiFolderReading();
    String str="E:\\file"; 
    mf.checkNoOfFiles(str);
   }
}

请也添加一些说明。
d4Rk 2015年

0

在Guava中,您不必等待Collection返回给您,但实际上可以遍历文件。很容易想象IDoSomethingWithThisFile下面的函数签名中的接口:

public static void collectFilesInDir(File dir) {
    TreeTraverser<File> traverser = Files.fileTreeTraverser();
    FluentIterable<File> filesInPostOrder = traverser.preOrderTraversal(dir);
    for (File f: filesInPostOrder)
        System.out.printf("File: %s\n", f.getPath());
}

TreeTraverser还允许您在各种遍历样式之间进行切换。


0
public class GetFilesRecursive {
    public static List <String> getFilesRecursively(File dir){
        List <String> ls = new ArrayList<String>();
        for (File fObj : dir.listFiles()) {
            if(fObj.isDirectory()) {
                ls.add(String.valueOf(fObj));
                ls.addAll(getFilesRecursively(fObj));               
            } else {
                ls.add(String.valueOf(fObj));       
            }
        }

        return ls;
    }
    public static List <String> getListOfFiles(String fullPathDir) {
        List <String> ls = new ArrayList<String> ();
        File f = new File(fullPathDir);
        if (f.exists()) {
            if(f.isDirectory()) {
                ls.add(String.valueOf(f));
                ls.addAll(getFilesRecursively(f));
            }
        } else {
            ls.add(fullPathDir);
        }
        return ls;
    }

    public static void main(String[] args) {
        List <String> ls = getListOfFiles("/Users/srinivasab/Documents");
        for (String file:ls) {
            System.out.println(file);
        }
        System.out.println(ls.size());
    }
}

0

另一个优化的代码

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class GetFilesRecursive {
    public static List <String> getFilesRecursively(File dir){
        List <String> ls = new ArrayList<String>();
        if (dir.isDirectory())
            for (File fObj : dir.listFiles()) {
                if(fObj.isDirectory()) {
                    ls.add(String.valueOf(fObj));
                    ls.addAll(getFilesRecursively(fObj));               
                } else {
                    ls.add(String.valueOf(fObj));       
                }
            }
        else
            ls.add(String.valueOf(dir));

        return ls;
    }

    public static void main(String[] args) {
        List <String> ls = getFilesRecursively(new File("/Users/srinivasab/Documents"));
        for (String file:ls) {
            System.out.println(file);
        }
        System.out.println(ls.size());
    }
}

请,您可以用更详细的解释来扩展您的答案吗?这对于理解非常有用。谢谢!
vezunchik

0

使用Java 8列出文件和目录的另一个示例 filter

public static void main(String[] args) {

System.out.println("Files!!");
        try {
            Files.walk(Paths.get("."))
                    .filter(Files::isRegularFile)
                    .filter(c ->
                            c.getFileName().toString().substring(c.getFileName().toString().length()-4).contains(".jpg")
                            ||
                            c.getFileName().toString().substring(c.getFileName().toString().length()-5).contains(".jpeg")
                    )
                    .forEach(System.out::println);

        } catch (IOException e) {
        System.out.println("No jpeg or jpg files");
        }

        System.out.println("\nDirectories!!\n");
        try {
            Files.walk(Paths.get("."))
                    .filter(Files::isDirectory)
                    .forEach(System.out::println);

        } catch (IOException e) {
            System.out.println("No Jpeg files");
        }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.