Je souhaite lister tous les fichiers d'un répertoire et les sous-répertoires de ce répertoire qui correspondent à un masque de fichier .
Par exemple "M: \ SOURCE \ *. Doc" tandis que SOURCE peut ressembler à ceci:
Files.walk(path, Integer.MAX_VALUE);
Doit renvoyer File1.doc et File2.doc.
Au départ, j'utilise un DirectoryStream , car cela vérifie déjà la syntaxe du masque / glob ainsi que de pouvoir l'utiliser pour le filtrage car ce n'est pas juste une expression régulière, mais un masque de fichier réel qu'un utilisateur régulier trouve plus facile à comprendre
Files.newDirectoryStream(path, mask);
Le problème est qu'un DirectoryStream ne vérifie que le répertoire de chemin immédiat que vous fournissez et non ses sous-répertoires
ALORS vient une méthode "d'aplatissement" avec Files.walk qui est en fait capable de regarder à travers tous les sous-répertoires, le problème est, il NE fournit PAS la possibilité de "filtrer" par un masque de fichier de la même manière qu'un DirectoryStream
|-- SOURCE | |-- Folder1 | | |-- File1.doc | | |-- File1.txt | |-- File2.doc | |-- File3.xml
Je suis donc bloqué, incapable de combiner le meilleur des deux méthodes ici ...
3 Réponses :
Il est possible d'utiliser un filter
flux commun pour récupérer les noms de fichiers filtrés à partir de Files.walk
utilisant String::matches
avec l'expression régulière appropriée:
test\level01\level11\test.doc test\level02\test-level2.doc test\t1.doc test\t3.docx
Production
String mask = "*.doc*"; PathMatcher maskMatcher = FileSystems.getDefault().getPathMatcher("glob:**/" + mask); Files.walk(Paths.get(SOURCE_DIR)) .filter(path -> maskMatcher.matches(path)) .forEach(System.out::println);
Structure du répertoire d'entrée:
test\t1.doc test\t3.docx test\level01\level11\test.doc test\level02\test-level2.doc
Mise à jour
Une solution récursive est possible en utilisant newDirectoryStream
mais elle doit être convertie en Stream:
static Stream<Path> readFilesByMaskRecursively(Path start, String mask) { List<Stream<Path>> sub = new ArrayList<>(); try { sub.add(StreamSupport.stream( // read files by mask in current dir Files.newDirectoryStream(start, mask).spliterator(), false)); Files.newDirectoryStream(start, (path) -> path.toFile().isDirectory()) .forEach(path -> sub.add(recursive(path, mask))); } catch (IOException ioex) { ioex.printStackTrace(); } return sub.stream().flatMap(s -> s); // convert to Stream<Path> } // test readFilesByMaskRecursively(Paths.get(SOURCE_DIR), "*.doc*") .forEach(System.out::println);
Production:
â t1.doc â t2.txt â t3.docx â t4.bin â ââââlevel01 â â test.do â â â ââââlevel11 â test.doc â ââââlevel02 test-level2.doc
Mise à jour 2
Un préfixe **/
peut être ajouté au PathMatcher
pour traverser les limites du répertoire, puis la solution basée sur Files.walk
peut utiliser un filtre simplifié sans qu'il soit nécessaire de supprimer des entrées spécifiques:
test\level01\level11\test.doc test\level02\test-level2.doc test\t1.doc test\t3.docx
Sortie (identique à celle de la solution récursive):
final String SOURCE_DIR = "test"; Files.walk(Paths.get(SOURCE_DIR)); .filter(p -> p.getFileName().toString().matches(".*\\.docx?")) .forEach(System.out::println);
"avec une expression régulière appropriée", eh bien ... c'est un problème, je ne veux pas avoir à m'occuper d'expressions régulières, je veux que le masque de fichier que l'utilisateur saisisse fonctionne tout de suite. J'ai mentionné l'exemple "* .doc" mais ce n'est pas le seul masque de fichier qui peut être utilisé, avec cela, je devrai convertir chacun d'entre eux en une expression régulière appropriée.
ajout d'une solution récursive utilisant un masque de fichier commun
Vous pouvez également utiliser FileVisitor
[1] personnalisé, avec une combinaison de PathMatcher
[2], qui fonctionne parfaitement avec les GLOB.
Le code pourrait ressembler à ceci:
public static void main(String[] args) throws IOException { System.out.println(getFiles(Paths.get("/tmp/SOURCE"), "*.doc")); } public static List<Path> getFiles(final Path directory, final String glob) throws IOException { final var docFileVisitor = new GlobFileVisitor(glob); Files.walkFileTree(directory, docFileVisitor); return docFileVisitor.getMatchedFiles(); } public static class GlobFileVisitor extends SimpleFileVisitor<Path> { private final PathMatcher pathMatcher; private List<Path> matchedFiles = new ArrayList<>(); public GlobFileVisitor(final String glob) { this.pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + glob); } @Override public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes) throws IOException { if (pathMatcher.matches(path.getFileName())) { matchedFiles.add(path); } return FileVisitResult.CONTINUE; } public List<Path> getMatchedFiles() { return matchedFiles; } }
[1] https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/FileVisitor.html
[2] https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/PathMatcher.html
Je pense que j'aurais peut-être résolu ma propre question avec les informations reçues ici et d'autres questions mentionnant l'objet PathMatcher
final PathMatcher maskMatcher = FileSystems.getDefault() .getPathMatcher("glob:" + mask); final List<Path> matchedFiles = Files.walk(path) .collect(Collectors.toList()); final List<Path> filesToRemove = new ArrayList<>(matchedFiles.size()); matchedFiles.forEach(foundPath -> { if (!maskMatcher.matches(foundPath.getFileName()) || Files.isDirectory(foundPath)) { filesToRemove.add(foundPath); } }); matchedFiles.removeAll(filesToRemove);
Donc, fondamentalement .getPathMatcher("glob:" + mask);
est la même chose que le DirectoryStream faisait pour filtrer les fichiers
Il ne me reste plus qu'à filtrer la liste des chemins que j'obtiens avec Files.walk en supprimant les éléments qui ne correspondent pas à mon PathMatcher et qui ne sont pas de type File