Рекурсивно вывести список всех файлов в каталоге, используя nio.file.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());
}

С уважением.

8 ответов

Java 8 предоставляет хороший способ для этого:

Files.walk(path)

Этот метод возвращает Stream<Path>,

Создайте метод, который будет вызывать себя, если следующий элемент является каталогом

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

Проверьте 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();
 }

Если вы хотите избежать рекурсивного вызова функции и иметь список файлов, который является переменной-членом, вы можете использовать стек:

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

Это самая короткая реализация, которую я придумал:

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

Используя Rx Java, требование может быть решено несколькими способами, в то же время придерживаясь использования DirectoryStream из JDK.

Следующие комбинации дадут вам желаемый эффект, я бы объяснил их по порядку:

Подход 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();
            }
       });
    }

Этот подход находит детей заданного каталога, а затем отправляет детей в виде наблюдаемых. Если дочерний файл является файлом, он будет немедленно доступен подписчику, иначе flatMap() в строке X вызовет метод, рекурсивно передавая каждый подкаталог в качестве аргумента. Для каждого такого подкаталога 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() для обработки ошибок звучит немного странно, потому что четные ошибки отправляются как события в реактивном программировании. Так почему бы нам не использовать оператор, который выставляет такие ошибки как события.

Лучшая альтернатива с именем fromCallable() была добавлена ​​в Rx Java 2.x. Второй подход показывает использование этого.

Подход 2. Использование операторов flatMap() и fromCallable

Этот подход использует оператор fromCallable(), который принимает Callable в качестве аргумента. Поскольку мы хотим рекурсивный подход, ожидаемый результат от этого вызываемого объекта - это 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 , но для плоской карты в этой строке требуется Observable, на которую можно подписаться.

Строка Y в обоих подходах использует concatMap()

Потому что concatMap() можно удобно использовать, если мы не хотим параллелизма во внутренних подписках, сделанных flatmap().

В обоих подходах реализация метода isFolder выглядит следующим образом:

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

    return true;
}

Maven координаты для Java RX 2.0

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

Завершите реализацию: он будет читать каждый файл из подпапки просто для быстрой проверки

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

Попробуйте это.. он проходит через каждую папку и распечатывает обе папки, а также файлы:-

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

Попробуйте: вы получите список каталогов и путь к подкаталогу; Там может быть неограниченный подкаталог, попробуйте использовать 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;
    }
}
Другие вопросы по тегам