CodeGym /Cours /JAVA 25 SELF /Globbing/PathMatcher, DirectoryStream.Filter

Globbing/PathMatcher, DirectoryStream.Filter

JAVA 25 SELF
Niveau 39 , Leçon 2
Disponible

1. Introduction

Lorsque vous travaillez avec des fichiers et des dossiers, il est souvent nécessaire de ne sélectionner que certains fichiers : par exemple, tous les fichiers ".java", toutes les images, tous les journaux à une date donnée. Pour cela, en Java (et pas seulement), on utilise le globbing (glob) et les expressions régulières (regex).

  • Globbing — un moyen simple de décrire un motif de nom de fichier à l’aide de caractères spéciaux (*, ?, [], {}), comme dans la ligne de commande Linux ou Windows.
  • Regex — un langage puissant d’expressions régulières pour des motifs complexes.

Exemples de motifs de globbing

  • *.java — tous les fichiers avec l’extension ".java" dans le dossier courant.
  • **/*.java — tous les fichiers ".java" dans tous les sous-dossiers (double astérisque — recherche récursive).
  • *.{png,jpg} — tous les fichiers avec l’extension ".png" ou ".jpg".
  • file-??.log — des fichiers comme "file-01.log", "file-AB.log" (deux caractères quelconques).
  • [A-Z]*.txt — tous les fichiers ".txt" commençant par une lettre majuscule.

Le globbing est plus simple que regex et, le plus souvent, il suffit pour filtrer des fichiers.

Comparaison entre glob et regex

Caractéristique glob regex
Simplicité Très simple Plus complexe, mais plus puissant
Symboles
*, ?, [], {}
Toutes les capacités de regex
Exemples
*.java
.*\.java
Récursivité
**/*.java
Pas de prise en charge intégrée
Quand l’utiliser Filtrage de fichiers Vérifications complexes

2. PathMatcher : filtrage de fichiers par motif

En Java NIO (java.nio.file), pour filtrer les fichiers selon un motif, on utilise l’interface PathMatcher. On peut l’obtenir via FileSystems.getDefault().getPathMatcher(...).

Syntaxe

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
  • "glob:*.java" — motif de globbing.
  • "regex:.*\\.java" — motif d’expression régulière.

Exemple : filtrer les fichiers dans un dossier

import java.nio.file.*;

Path dir = Paths.get("src");
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");

try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
    for (Path entry : stream) {
        if (matcher.matches(entry.getFileName())) {
            System.out.println(entry);
        }
    }
}

Important :

  • matcher.matches() vérifie généralement uniquement le nom de fichier — passez entry.getFileName(), et non le chemin complet.
  • Pour une recherche récursive, utilisez Files.walk() ou Files.find().

Files.walk() est une méthode NIO2 qui renvoie un Stream<Path> avec tous les fichiers et dossiers dans le répertoire indiqué, de manière récursive, y compris les sous-dossiers. À la différence de DirectoryStream, qui montre uniquement le contenu d’un seul dossier, Files.walk() permet de travailler avec l’arborescence via l’API Stream.

Exemple d’utilisation :

import java.nio.file.*;
import java.util.stream.Stream;

Path start = Paths.get("src");
try (Stream<Path> stream = Files.walk(start)) { // tous les sous-répertoires de manière récursive
    stream.filter(Files::isRegularFile)
          .forEach(System.out::println);
}

Exemple avec regex

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("regex:.*\\.(png|jpg)");

3. Files.newDirectoryStream : filtrage lors de l’affichage d’un dossier

La méthode Files.newDirectoryStream() permet de filtrer directement les fichiers selon un motif ou à l’aide de votre propre filtre.

Filtrage par motif glob

try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
    for (Path entry : stream) {
        System.out.println(entry);
    }
}

Le second paramètre est un motif glob (sans le préfixe "glob:").

Filtrage avec DirectoryStream.Filter

Si vous avez besoin d’une logique plus complexe (par exemple, filtrage par taille, date, exclusion de dossiers), utilisez DirectoryStream.Filter<Path> :

DirectoryStream.Filter<Path> filter = path -> {
    // Exemple : seulement des fichiers, pas des dossiers, et pas .git ni node_modules
    return Files.isRegularFile(path)
        && !path.getFileName().toString().equals(".git")
        && !path.getFileName().toString().equals("node_modules");
};

try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
    for (Path entry : stream) {
        System.out.println(entry);
    }
}

4. Files.find : sélection puissante avec BiPredicate

Si vous devez rechercher des fichiers selon des critères complexes (par exemple, par date, taille, nom, de manière récursive), utilisez Files.find.

Syntaxe

Stream<Path> stream = Files.find(
    startDir, // où chercher
    maxDepth, // profondeur (Integer.MAX_VALUE — récursif)
    (path, attrs) -> {
        // path — chemin du fichier
        // attrs — attributs du fichier (taille, date, etc.)
        return path.getFileName().toString().endsWith(".log")
            && attrs.size() > 1024; // uniquement des logs volumineux
    }
);

stream.forEach(System.out::println);
  • Le deuxième paramètre est la profondeur maximale de recherche.
  • Le troisième — BiPredicate<Path, BasicFileAttributes> : renvoie true si le fichier correspond.

Exemple : exclure les dossiers .git et node_modules

Stream<Path> stream = Files.find(
    Paths.get("."),
    Integer.MAX_VALUE,
    (path, attrs) -> {
        String name = path.getFileName().toString();
        // Exclure les dossiers .git et node_modules
        if (name.equals(".git") || name.equals("node_modules")) return false;
        // Uniquement les fichiers .log de taille supérieure à 1 Mo
        return name.endsWith(".log") && attrs.size() > 1024 * 1024;
    }
);
stream.forEach(System.out::println);

5. Pratique

Exemple 1 : Trouver tous les fichiers .log, sauf .git et node_modules

Files.walk(Paths.get("."))
    .filter(path -> {
        String name = path.getFileName().toString();
        // Exclure les dossiers
        if (name.equals(".git") || name.equals("node_modules")) return false;
        // Uniquement les fichiers .log
        return name.endsWith(".log");
    })
    .forEach(System.out::println);

Exemple 2 : Trouver toutes les images créées après une date donnée

import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;

Instant after = Instant.parse("2024-06-01T00:00:00Z");

Files.find(
    Paths.get("images"),
    Integer.MAX_VALUE,
    (path, attrs) -> {
        String name = path.getFileName().toString();
        return (name.endsWith(".png") || name.endsWith(".jpg"))
            && attrs.creationTime().toInstant().isAfter(after);
    }
).forEach(System.out::println);

Exemple 3 : Trouver tous les gros fichiers (plus de 10 Mo), sauf .git

Files.find(
    Paths.get("."),
    Integer.MAX_VALUE,
    (path, attrs) -> {
        String name = path.getFileName().toString();
        return !name.equals(".git") && attrs.size() > 10 * 1024 * 1024;
    }
).forEach(System.out::println);

6. Détails utiles

Aide-mémoire rapide de la syntaxe glob

  • * — n’importe quel nombre de caractères (sauf le séparateur /)
  • ** — n’importe quel nombre de sous-dossiers (fonctionne en Java)
  • ? — exactement un caractère quelconque
  • [abc] — l’un des caractères a, b, c
  • [a-z] — tout caractère de l’intervalle
  • {a,b,c} — l’une des valeurs listées (par exemple, *.{png,jpg})

Exemples :

  • *.java — tous les fichiers ".java" dans le dossier courant
  • **/*.java — tous les fichiers ".java" dans tous les sous-dossiers
  • file-??.log — des fichiers comme "file-01.log", "file-AB.log"
  • [A-Z]*.txt — tous les fichiers ".txt" commençant par une lettre majuscule

Performances et fuites : fermez DirectoryStream !

Important !

  • DirectoryStream et les flux renvoyés par Files.find/Files.walk — ce sont des ressources qu’il faut fermer.
  • Utilisez le try-with-resources :
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
    for (Path entry : stream) {
        // ...
    }
}
  • Si vous ne fermez pas le flux, une fuite de ressources peut survenir (par exemple, « trop de fichiers ouverts »).
  • Pour Files.find et Files.walk — appelez impérativement close() ou utilisez try-with-resources :
try (Stream<Path> stream = Files.find(...)) {
    stream.forEach(System.out::println);
}

7. Conclusion et erreurs courantes

Erreur n° 1 : Utiliser un motif glob sans comprendre que * n’effectue pas de recherche récursive. Pour la récursivité, utilisez "**/*.java" ou Files.walk.

Erreur n° 2 : Passer le chemin complet à matcher.matches() — il faut généralement passer uniquement le nom de fichier (getFileName()).

Erreur n° 3 : Oublier de fermer DirectoryStream ou Stream<Path> — cela provoque une fuite de ressources.

Erreur n° 4 : Des motifs regex trop complexes pour un filtrage simple — utilisez glob si cela suffit.

Erreur n° 5 : Ne pas exclure les dossiers système (".git", "node_modules") — la recherche devient lente et est « polluée » par des fichiers superflus.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION