【问题标题】:Recursively list all files within a directory using nio.file.DirectoryStream;使用 nio.file.DirectoryStream 递归列出目录中的所有文件;
【发布时间】:2014-01-08 04:44:19
【问题描述】:

我想列出指定目录中的所有文件以及该目录中的子目录。不应列出任何目录。

我当前的代码如下。它不能正常工作,因为它只列出指定目录中的文件和目录。

我该如何解决这个问题?

final List<Path> files = new ArrayList<>();

Path path = Paths.get("C:\\Users\\Danny\\Documents\\workspace\\Test\\bin\\SomeFiles");
try
{
  DirectoryStream<Path> stream;
  stream = Files.newDirectoryStream(path);
  for (Path entry : stream)
  {
    files.add(entry);
  }
  stream.close();
}
catch (IOException e)
{
  e.printStackTrace();
}

for (Path entry: files)
{
  System.out.println(entry.toString());
}

【问题讨论】:

  • “不应列出任何目录”是什么意思
  • @BrianRoach 这是怎么重复的?我要求用 nio.file.DirectoryStream 解决问题。
  • @BrianRoach 唯一“赞成”的答案是直接回复该问题,并且还引用了 DirectoryStream,仍然不是重复的。
  • @BrianRoach 我要求使用 Java 7 nio 的方法。您认为我在重复一个问题,要求使用 Java 6 io 的方法。它们是不同的。请认清自己的错误。问候。

标签: java file nio directorystream


【解决方案1】:

Java 8 提供了一个很好的方法:

Files.walk(path)

此方法返回Stream&lt;Path&gt;

【讨论】:

【解决方案2】:

如果下一个元素是目录,则创建一个将调用自身的方法

void listFiles(Path path) throws IOException {
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
        for (Path entry : stream) {
            if (Files.isDirectory(entry)) {
                listFiles(entry);
            }
            files.add(entry);
        }
    }
}

【讨论】:

  • 应始终关闭流以释放资源:)
  • 如果您尝试使用带有过滤器参数的 Files.newDirectoryStream 路径,这将不起作用
  • @ACV 我很好奇,为什么不呢?
  • @AbhijitSarkar 我忘了,因为那是 2016 年。你去哪儿了?
  • @ACV 我去过。这就是未量化声明的问题,它们以后无法得到证实。如果我在 1999 年说2+2=4,我仍然能够在 2018 年证明这一点。
【解决方案3】:

检查FileVisitor,非常整洁。

 Path path= Paths.get("C:\\Users\\Danny\\Documents\\workspace\\Test\\bin\\SomeFiles");
 final List<Path> files=new ArrayList<>();
 try {
    Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
     @Override
     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
          if(!attrs.isDirectory()){
               files.add(file);
          }
          return FileVisitResult.CONTINUE;
      }
     });
 } catch (IOException e) {
      e.printStackTrace();
 }

【讨论】:

  • 我觉得它不像 Evgniy 的解决方案那么简洁?
  • 这是主观的。我喜欢 FileVisitor 方法。
  • walkFileTree() 的问题在于它是一个内部类,并且由于 java 不支持闭包,因此您无法访问父元素。如果你只是在操作文件,它工作得很好,但如果你需要从另一个范围引用它,那就不是一个很好的匹配。
  • @Fred Inner 类不适合当您的解决方案需要修改原始或不可变数据类型时,像 JS 这样的语言中的闭包允许您这样做。例如,获取最长的文件名。I相信这就是你在最后一句话中所说的。我的答案是在这个问题的背景下。
  • 我喜欢这种方法!
【解决方案4】:

如果你想避免函数递归调用自身并拥有一个作为成员变量的文件列表,你可以使用堆栈:

private List<Path> listFiles(Path path) throws IOException {
    Deque<Path> stack = new ArrayDeque<Path>();
    final List<Path> files = new LinkedList<>();

    stack.push(path);

    while (!stack.isEmpty()) {
        DirectoryStream<Path> stream = Files.newDirectoryStream(stack.pop());
        for (Path entry : stream) {
            if (Files.isDirectory(entry)) {
                stack.push(entry);
            }
            else {
                files.add(entry);
            }
        }
        stream.close();
    }

    return files;
}

【讨论】:

  • 递归使用隐式堆栈,使用外部堆栈的原因是什么?你有收获吗?
  • 没有大的区别,更多的是编码风格偏好。不过,这里有几个原因: 如果递归太深,您最终会遇到 StackOverflowException;您可能会争辩说,使用显式堆栈使代码更易于阅读。在这种情况下,您不会为每个级别的递归都打开流;如果您需要进行任何类型的预处理或后处理(例如错误处理)或在递归期间保留上下文(例如添加的文件列表),则不需要另一个函数调用此函数;
【解决方案5】:

这是我想出的最短的实现:

final List<Path> files = new ArrayList<>();
Path path = Paths.get("C:\\Users\\Danny\\Documents\\workspace\\Test\\bin\\SomeFiles");
try {
    Files.walk(path).forEach(entry -> list.add(entry));
} catch (IOException e) {
    e.printStackTrack();
}

【讨论】:

    【解决方案6】:

    使用 Rx Java,可以通过多种方式解决需求,同时坚持使用 JDK 中的 DirectoryStream。

    以下组合会给你想要的效果,我会按顺序解释:

    方法 1。使用 flatMap() 和 defer() 运算符的递归方法

    方法 2。使用 flatMap() 和 fromCallable 运算符的递归方法

    注意:如果您将 flatMap() 的使用替换为 concatMap(),目录树导航必然会发生在深度 -首次搜索(DFS)方式。使用flatMap(),不保证DFS效果。

    方法一:使用 flatMap() 和 defer()

       private Observable<Path> recursiveFileSystemNavigation_Using_Defer(Path dir) {
           return Observable.<Path>defer(() -> {
                //
                // try-resource block
                //
                try(DirectoryStream<Path> children = Files.newDirectoryStream(dir))
                {
                    //This intermediate storage is required because DirectoryStream can't be navigated more than once.
                    List<Path> subfolders = Observable.<Path>fromIterable(children)
                                                            .toList()
                                                            .blockingGet();
    
    
                    return Observable.<Path>fromIterable(subfolders)
                            /* Line X */    .flatMap(p -> !isFolder(p) ? Observable.<Path> just(p) : recursiveFileSystemNavigation_Using_Defer(p), Runtime.getRuntime().availableProcessors());
    
                    //      /* Line Y */  .concatMap(p -> !isFolder(p) ? Observable.<Path> just(p) : recursiveFileSystemNavigation_Using_Defer(p));
    
                } catch (IOException e) {
                    /*
                     This catch block is required even though DirectoryStream is  Closeable
                     resource. Reason is that .close() call on a DirectoryStream throws a 
                     checked exception.
                    */
                    return Observable.<Path>empty();
                }
           });
        }
    

    这种方法是查找给定目录的子目录,然后将子目录作为 Observable 发出。如果一个孩子是一个文件,它将立即可供订阅者使用,否则 Line X 上的 flatMap() 将调用该方法,递归地将每个子目录作为参数传递。对于每个这样的子目录,flatmap 将同时在内部订阅它们的子目录。这就像一个需要控制的连锁反应。

    因此使用 Runtime.getRuntime().availableProcessors() 为 flatmap() 设置 最大并发级别 并防止它同时订阅所有子文件夹.在不设置并发级别的情况下,想象一下当一个文件夹有 1000 个子文件夹时会发生什么。

    defer() 的使用可防止过早地创建 DirectoryStream,并确保仅在进行真​​正的订阅以查找其子文件夹时才会发生。

    最后,该方法返回一个 Observable 以便客户端可以订阅并对结果做一些有用的事情,如下所示:

    //
    // Using the defer() based approach
    //
    recursiveDirNavigation.recursiveFileSystemNavigation_Using_Defer(startingDir)
                        .subscribeOn(Schedulers.io())
                        .observeOn(Schedulers.from(Executors.newFixedThreadPool(1)))
                        .subscribe(p -> System.out.println(p.toUri()));
    

    使用 defer() 的缺点是,如果它的参数函数抛出一个检查异常,它就不能很好地处理检查异常。因此,即使 DirectoryStream(实现 Closeable) 是在 try-resource 块中创建的,我们仍然必须捕获 IOException,因为 DirectoryStream 的自动关闭会抛出该检查异常.

    在使用基于 Rx 的样式时,使用 catch() 块进行错误处理听起来有点奇怪,因为在反应式编程中偶数错误会作为事件发送。那么我们为什么不使用将此类错误作为事件公开的运算符。

    Rx Java 2.x 中添加了一个更好的替代方案,名为 fromCallable()。第二种方法显示了它的用法。

    方法 2. 使用 flatMap() 和 fromCallable 运算符

    这种方法使用 fromCallable() 运算符,它以 Callable 作为参数。由于我们想要一种递归方法,因此该可调用对象的预期结果是给定文件夹的子对象的 Observable。由于我们希望订阅者在可用时接收结果,因此我们需要从该方法返回一个 Observable。由于内部可调用的结果是一个 Observable 子级列表,因此最终效果是一个 Observable 的 Observable。

       private Observable<Observable<Path>> recursiveFileSystemNavigation_WithoutExplicitCatchBlock_UsingFromCallable(Path dir) {
           /*
            * fromCallable() takes a Callable argument. In this case the callbale's return value itself is 
            * a list of sub-paths therefore the overall return value of this method is Observable<Observable<Path>>
            * 
            * While subscribing the final results, we'd flatten this return value.
            * 
            * Benefit of using fromCallable() is that it elegantly catches the checked exceptions thrown 
            * during the callable's call and exposes that via onError() operator chain if you need. 
            * 
            * Defer() operator does not give that flexibility and you have to explicitly catch and handle appropriately.   
            */
           return Observable.<Observable<Path>> fromCallable(() -> traverse(dir))
                                            .onErrorReturnItem(Observable.<Path>empty());
    
        }
    
        private Observable<Path> traverse(Path dir) throws IOException {
            //
            // try-resource block
            //
            try(DirectoryStream<Path> children = Files.newDirectoryStream(dir))
            {
                //This intermediate storage is required because DirectoryStream can't be navigated more than once.
                List<Path> subfolders = Observable.<Path>fromIterable(children)
                                                        .toList()
                                                        .blockingGet();
    
                return Observable.<Path>fromIterable(subfolders)
                        /* Line X */    .flatMap(p -> ( !isFolder(p) ? Observable.<Path> just(p) : recursiveFileSystemNavigation_WithoutExplicitCatchBlock_UsingFromCallable(p).blockingSingle())
                                                 ,Runtime.getRuntime().availableProcessors());
    
                //      /* Line Y */  .concatMap(p -> ( !isFolder(p) ? Observable.<Path> just(p) : recursiveFileSystemNavigation_WithoutExplicitCatchBlock_UsingFromCallable(p).blockingSingle() ));
    
            }
        }
    

    然后订阅者需要将结果流展平,如下所示:

    //
    // Using the fromCallable() based approach
    //
    recursiveDirNavigation.recursiveFileSystemNavigation_WithoutExplicitCatchBlock_UsingFromCallable(startingDir)
                            .subscribeOn(Schedulers.io())
                            .flatMap(p -> p)
                            .observeOn(Schedulers.from(Executors.newFixedThreadPool(1)))
                            .subscribe(filePath -> System.out.println(filePath.toUri()));
    

    traverse()方法中为什么X行使用阻塞Get

    因为递归函数返回一个Observable,但是该行的flatmap需要一个Observable来订阅。

    两种方法中的 Y 行都使用 concatMap()

    因为如果我们不希望在 flatmap() 进行的内部订阅期间出现并行性,那么 concatMap() 可以很方便地使用。

    在这两种方法中,方法 isFolder 的实现如下所示:

    private boolean isFolder(Path p){
        if(p.toFile().isFile()){
            return false;
        }
    
        return true;
    }
    

    Java RX 2.0 的 Maven 坐标

    <dependency>
        <groupId>io.reactivex.rxjava2</groupId>
        <artifactId>rxjava</artifactId>
        <version>2.0.3</version>
    </dependency>
    

    Java 文件中的导入

    import java.io.IOException;
    import java.nio.file.DirectoryStream;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.List;
    import java.util.concurrent.Executors;
    import io.reactivex.Observable;
    import io.reactivex.schedulers.Schedulers;
    

    【讨论】:

      【解决方案7】:

      完成实现:它会从子文件夹中读取每个文件,只是快速检查一下

      Path configFilePath = FileSystems.getDefault().getPath("C:\\Users\\sharmaat\\Desktop\\issue\\stores");
      List<Path> fileWithName = Files.walk(configFilePath)
                      .filter(s -> s.toString().endsWith(".java"))
                      .map(Path::getFileName)
                      .sorted()
                      .collect(Collectors.toList());
      
      for (Path name : fileWithName) {
          // printing the name of file in every sub folder
          System.out.println(name);
      }
      

      【讨论】:

        【解决方案8】:

        试试这个..它遍历每个文件夹并打印文件夹和文件:-

        public static void traverseDir(Path path) {
            try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
                for (Path entry : stream) {
                    if (Files.isDirectory(entry)) {
                        System.out.println("Sub-Folder Name : " + entry.toString());
                        traverseDir(entry);
                    } else {
                        System.out.println("\tFile Name : " + entry.toString());
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        

        【讨论】:

          【解决方案9】:

          Try : 你会得到一个目录列表和子目录路径; 可能有无限子目录,尽量使用recursive进程。

          public class DriectoryFileFilter {
              private List<String> filePathList = new ArrayList<String>();
          
              public List<String> read(File file) {
                  if (file.isFile()) {
                      filePathList.add(file.getAbsolutePath());
                  } else if (file.isDirectory()) {
                      File[] listOfFiles = file.listFiles();
                      if (listOfFiles != null) {
                          for (int i = 0; i < listOfFiles.length; i++){
                              read(listOfFiles[i]);
                          }
                      } else {
                          System.out.println("[ACCESS DENIED]");
                      }
                  }
                  return filePathList;
              }
          }
          

          【讨论】:

          • 感谢您的回答,但是,这不使用问题中指定的 nio.file.DirectoryStream 。问候。
          • 是的,它只使用java.io.
          猜你喜欢
          • 2010-10-19
          • 2017-10-31
          • 2010-10-04
          • 2010-10-30
          • 1970-01-01
          • 1970-01-01
          • 2014-09-07
          • 2011-07-11
          • 2014-10-13
          相关资源
          最近更新 更多