使用nio.file.DirectoryStream以递归方式列出目录中的所有文件;

时间:2014-01-08 04:44:20

标签: java file nio directorystream

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

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

我该如何解决这个问题?

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());
}

9 个答案:

答案 0 :(得分:56)

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

Files.walk(path)

此方法返回Stream<Path>

答案 1 :(得分:30)

创建一个方法,如果下一个元素是目录

,它将调用自身
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);
        }
    }
}

答案 2 :(得分:25)

检查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();
 }

答案 3 :(得分:6)

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

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;
}

答案 4 :(得分:2)

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

以下组合将为您提供所需的效果,我将按顺序解释它们:

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

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

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

方法1:使用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();
            }
       });
    }

这种方法是查找给定目录的子项,然后将子项作为Observables发送。如果一个子文件是一个文件,它将立即供订阅者使用,第十行上的flatMap()将以递归方式传递每个子目录作为参数。对于每个这样的子目录,flatmap将在内部同时订阅他们的子目录。这就像需要控制的连锁反应。

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

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

最后,该方法返回 Observable&lt;路径&gt; ,以便客户可以订阅并对结果执行有用的操作,如下所示:

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

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

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

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

此方法使用 fromCallable()运算符,该运算符将 Callable 作为参数。由于我们需要递归方法,因此可调用的预期结果是给定文件夹的子节点Observable。由于我们希望订阅者在可用时接收结果,因此我们需要从此方法返回Observable。由于内部可调用的结果是一个Observable子列表,因此净效应是一个Observable of Observables。

   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&lt; Observable&gt;,但该行的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;

答案 5 :(得分:2)

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

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 :(得分:2)

完成实现:只需快速检查即可从子文件夹读取每个文件

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

答案 7 :(得分:0)

尝试此.. ..遍历每个文件夹并打印文件夹和文件: -

onmessage

答案 8 :(得分:-1)

尝试:您将获得目录和子目录路径的列表; 可能存在无限的子目录,尝试使用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;
    }
}