Listar recursivamente todos os arquivos dentro de um diretório usando nio.file.DirectoryStream;

Eu quero listar todos os arquivos dentro do diretório especificado e subdiretórios dentro desse diretório. Nenhum diretório deve ser listado.

Meu código atual está abaixo. Ele não funciona corretamente, pois lista apenas os arquivos e diretórios dentro do diretório especificado.

Como posso consertar isso?

final List files = new ArrayList(); Path path = Paths.get("C:\\Users\\Danny\\Documents\\workspace\\Test\\bin\\SomeFiles"); try { DirectoryStream 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()); } 

Saudações.

O Java 8 oferece uma boa maneira para isso:

 Files.walk(path) 

Este método retorna o Stream .

Faça um método que se chama se o próximo elemento for o diretório

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

Verifique FileVisitor , muito legal.

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

Se você quiser evitar que a function se chame recursivamente e tenha uma lista de arquivos que seja uma variável de membro, você pode usar uma pilha:

 private List listFiles(Path path) throws IOException { Deque stack = new ArrayDeque(); final List files = new LinkedList<>(); stack.push(path); while (!stack.isEmpty()) { DirectoryStream stream = Files.newDirectoryStream(stack.pop()); for (Path entry : stream) { if (Files.isDirectory(entry)) { stack.push(entry); } else { files.add(entry); } } stream.close(); } return files; } 

Usando o Rx Java, o requisito pode ser resolvido de várias maneiras, ao mesmo tempo em que se mantém o uso do DirectoryStream do JDK.

As combinações seguintes lhe darão o efeito desejado, eu as explicaria em sequência:

Abordagem 1 . Uma abordagem recursiva usando operadores flatMap () e defer ()

Abordagem 2 . Uma abordagem recursiva usando operadores flatMap () e fromCallable

Nota: Se você replace o uso de flatMap () por concatMap () , a navegação da tree de diretórios ocorrerá necessariamente em uma maneira DFS (depth-first-search). Com flatMap (), o efeito DFS não é garantido.

Abordagem 1: Usando flatMap () e defer ()

  private Observable recursiveFileSystemNavigation_Using_Defer(Path dir) { return Observable.defer(() -> { // // try-resource block // try(DirectoryStream children = Files.newDirectoryStream(dir)) { //This intermediate storage is required because DirectoryStream can't be navigated more than once. List subfolders = Observable.fromIterable(children) .toList() .blockingGet(); return Observable.fromIterable(subfolders) /* Line X */ .flatMap(p -> !isFolder(p) ? Observable. just(p) : recursiveFileSystemNavigation_Using_Defer(p), Runtime.getRuntime().availableProcessors()); // /* Line Y */ .concatMap(p -> !isFolder(p) ? Observable. 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.empty(); } }); } 

Essa abordagem é encontrar crianças de determinado diretório e depois emitir os filhos como Observáveis. Se um filho é um arquivo, ele estará imediatamente disponível para um assinante, caso contrário flatMap () na Linha X invocará o método recursivamente passando cada subdiretório como argumento. Para cada um desses subdiretórios, o flatmap irá internamente assinar seus filhos todos ao mesmo tempo. Isto é como uma reação em cadeia que precisa ser controlada.

Portanto, o uso de Runtime.getRuntime (). AvailableProcessors () define o nível máximo de simultaneidade para flatmap () e evita que ele se inscreva em todas as subpastas ao mesmo tempo. Sem definir o nível de simultaneidade, imagine o que acontecerá quando uma pasta tiver 1.000 filhos.

O uso de defer () impede a criação de um DirectoryStream prematuramente e garante que isso acontecerá apenas quando uma assinatura real para localizar suas subpastas for feita.

Finalmente, o método retorna um Observable para que um cliente possa se inscrever e fazer algo útil com os resultados, conforme mostrado abaixo:

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

A desvantagem de usar defer () é que ele não lida bem com exceções verificadas se sua function de argumento estiver lançando uma exceção verificada. Portanto, mesmo que DirectoryStream (que implementa Closeable) tenha sido criado em um bloco try-resource, ainda precisamos capturar a IOException porque o encerramento automático de um DirectoryStream gera essa exceção verificada.

Ao usar o estilo baseado em Rx, o uso de blocos catch () para tratamento de erros soa um pouco estranho, porque até os erros são enviados como events na programação reativa. Então, por que não usamos um operador que expõe esses erros como events?

Uma alternativa melhor chamada de fromCallable () foi adicionada no Rx Java 2.x. A segunda abordagem mostra o uso dela.

Abordagem 2. Usando operadores flatMap () e fromCallable

Essa abordagem usa o operador fromCallable () , que recebe um Callable como argumento. Como queremos uma abordagem recursiva, o resultado esperado desse callable é um Observable de children of given folder. Como queremos que um assinante receba resultados quando eles estiverem disponíveis, precisamos retornar um Observável deste método. Como o resultado de call interno é uma lista observável de filhos, o efeito líquido é um Observable of Observables.

  private Observable> 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> * * 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.> fromCallable(() -> traverse(dir)) .onErrorReturnItem(Observable.empty()); } private Observable traverse(Path dir) throws IOException { // // try-resource block // try(DirectoryStream children = Files.newDirectoryStream(dir)) { //This intermediate storage is required because DirectoryStream can't be navigated more than once. List subfolders = Observable.fromIterable(children) .toList() .blockingGet(); return Observable.fromIterable(subfolders) /* Line X */ .flatMap(p -> ( !isFolder(p) ? Observable. just(p) : recursiveFileSystemNavigation_WithoutExplicitCatchBlock_UsingFromCallable(p).blockingSingle()) ,Runtime.getRuntime().availableProcessors()); // /* Line Y */ .concatMap(p -> ( !isFolder(p) ? Observable. just(p) : recursiveFileSystemNavigation_WithoutExplicitCatchBlock_UsingFromCallable(p).blockingSingle() )); } } 

Um assinante precisará então achatar o stream de resultados conforme mostrado abaixo:

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

No método traverse (), por que a linha X está bloqueando?

Porque a function recursiva retorna um Observable , mas a flatmap nessa linha precisa de um Observable para se inscrever.

A linha Y em ambas as abordagens usa concatMap ()

Porque concatMap () pode ser usado confortavelmente se não quisermos o paralelismo durante inscrições internas feitas pelo flatmap ().

Em ambas as abordagens, a implementação do método isFolder se parece com abaixo:

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

Coordenadas Maven para o Java RX 2.0

  io.reactivex.rxjava2 rxjava 2.0.3  

Importações no arquivo 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; 

Esta é a implementação mais curta que eu criei:

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

Tente isto .. atravessa todas as pastas e imprime tanto pasta como arquivos: –

 public static void traverseDir(Path path) { try (DirectoryStream 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(); } } 

Tente: Você obterá uma lista do caminho do diretório e do subdiretório; Pode haver um subdiretório ilimitado, tente usar o processo recursive .

 public class DriectoryFileFilter { private List filePathList = new ArrayList(); public List 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; } }