Подтвердить что ты не робот

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

С уважением.

4b9b3361

Ответ 1

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

Files.walk(path)

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

Ответ 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);
        }
    }
}

Ответ 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();
 }

Ответ 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;
}

Ответ 5

Используя 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();
            }
       });
    }

Этот подход находит детей данного каталога, а затем испускает детей как Наблюдаемые. Если дочерний файл является файлом, он будет немедленно доступен подписчику. Else FlatMap() в Линии X вызовет метод, рекурсивно передающий каждый подкаталог в качестве аргумента. Для каждого такого subdir, flatmap будет внутренне подписываться на своих детей в одно и то же время. Это похоже на цепную реакцию, которую нужно контролировать.

Поэтому использование Runtime.getRuntime(). availableProcessors() устанавливает максимум concurrency уровень для flatmap() и не позволяет ему подписаться на все подпапки на в то же время. Не устанавливая уровень concurrency, представьте, что произойдет, если в папке было 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 из этого метода. Поскольку результатом внутреннего вызываемого является список наблюдаемых дочерних элементов, то чистый эффект является наблюдаемым наблюдением.

   private Observable<Observable<Path>> recursiveFileSystemNavigation_WithoutExplicitCatchBlock_UsingFromCallable(Path dir) {
       /*
        * fromCallable() takes a Callable argument. In this case the callbale 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 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> , но для этой строки требуется подписка на Observable.

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

Так как concatMap() можно удобно использовать, если мы не хотим parallelism во время постоянных подписки, сделанных 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;

Ответ 6

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

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

Ответ 7

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