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

Globbing/PathMatcher, DirectoryStream.Filter

JAVA 25 SELF
Livello 39, Lezione 2
Disponibile

1. Introduzione

Quando lavorate con file e cartelle, spesso è necessario selezionare solo determinati file: per esempio, tutti i file ".java", tutte le immagini, tutti i log per una certa data. Per questo in Java (e non solo) si usano il globbing (glob) e le espressioni regolari (regex).

  • Globbing — un modo semplice per descrivere un pattern di nome file con caratteri speciali (*, ?, [], {}), come nella riga di comando di Linux o Windows.
  • Regex — un linguaggio potente di espressioni regolari per pattern complessi.

Esempi di pattern globbing

  • *.java — tutti i file con estensione ".java" nella cartella corrente.
  • **/*.java — tutti i file ".java" in tutte le sottocartelle (doppio asterisco — ricerca ricorsiva).
  • *.{png,jpg} — tutti i file con estensione ".png" o ".jpg".
  • file-??.log — file come "file-01.log", "file-AB.log" (due caratteri qualsiasi).
  • [A-Z]*.txt — tutti i file ".txt" che iniziano con una lettera maiuscola.

Il globbing è più semplice del regex e nella maggior parte dei casi è sufficiente per filtrare i file.

Confronto tra glob e regex

Caratteristica glob regex
Semplicità Molto semplice Più complesso ma più potente
Simboli
*, ?, [], {}
Tutte le funzionalità di regex
Esempi
*.java
.*\.java
Ricorsività
**/*.java
Nessun supporto integrato
Quando usarlo Filtraggio dei file Verifiche complesse

2. PathMatcher: filtraggio dei file per maschera

In Java NIO (java.nio.file) per filtrare i file per maschera si usa l'interfaccia PathMatcher. La si può ottenere tramite FileSystems.getDefault().getPathMatcher(...).

Sintassi

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
  • "glob:*.java" — pattern di globbing.
  • "regex:.*\\.java" — pattern regex.

Esempio: filtrare i file in una cartella

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

Importante:

  • matcher.matches() in genere verifica solo il nome del file — passate entry.getFileName(), non il percorso completo.
  • Per la ricerca ricorsiva usate Files.walk() o Files.find().

Files.walk() — un metodo di NIO2 che restituisce uno Stream<Path> con tutti i file e le cartelle nella directory indicata in modo ricorsivo, incluse le sottocartelle. A differenza di DirectoryStream, che mostra solo il contenuto di una singola cartella, Files.walk() consente di lavorare con l’albero delle directory tramite lo Stream API.

Esempio d'uso:

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

Path start = Paths.get("src");
try (Stream<Path> stream = Files.walk(start)) { // ricorsivo su tutte le sottocartelle
    stream.filter(Files::isRegularFile)
          .forEach(System.out::println);
}

Esempio con regex

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

3. Files.newDirectoryStream: filtraggio durante l'esplorazione della cartella

Il metodo Files.newDirectoryStream() consente di filtrare subito i file per maschera oppure mediante un filtro personalizzato.

Filtraggio per pattern glob

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

Il secondo parametro è un pattern glob (senza il prefisso "glob:").

Filtraggio con DirectoryStream.Filter

Se serve una logica più complessa (ad esempio filtraggio per dimensione, data, esclusione di cartelle), usate DirectoryStream.Filter<Path>:

DirectoryStream.Filter<Path> filter = path -> {
    // Esempio: solo file, non cartelle, e non .git, non 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: selezione potente con BiPredicate

Se dovete cercare file in base a condizioni complesse (ad esempio, per data, dimensione, nome, in modo ricorsivo), usate Files.find.

Sintassi

Stream<Path> stream = Files.find(
    startDir, // dove cercare
    maxDepth, // profondità (Integer.MAX_VALUE — ricorsivo)
    (path, attrs) -> {
        // path — percorso del file
        // attrs — attributi del file (dimensione, data, ecc.)
        return path.getFileName().toString().endsWith(".log")
            && attrs.size() > 1024; // solo log grandi
    }
);

stream.forEach(System.out::println);
  • Il secondo parametro è la profondità massima di ricerca.
  • Il terzo è un BiPredicate<Path, BasicFileAttributes>: restituisce true se il file corrisponde.

Esempio: escludere le cartelle .git e node_modules

Stream<Path> stream = Files.find(
    Paths.get("."),
    Integer.MAX_VALUE,
    (path, attrs) -> {
        String name = path.getFileName().toString();
        // Escludiamo le cartelle .git e node_modules
        if (name.equals(".git") || name.equals("node_modules")) return false;
        // Solo file .log con dimensione maggiore di 1 MB
        return name.endsWith(".log") && attrs.size() > 1024 * 1024;
    }
);
stream.forEach(System.out::println);

5. Pratica

Esempio 1: trovare tutti i file .log, esclusi .git e node_modules

Files.walk(Paths.get("."))
    .filter(path -> {
        String name = path.getFileName().toString();
        // Escludiamo le cartelle
        if (name.equals(".git") || name.equals("node_modules")) return false;
        // Solo file .log
        return name.endsWith(".log");
    })
    .forEach(System.out::println);

Esempio 2: trovare tutte le immagini create dopo una certa data

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

Esempio 3: trovare tutti i file grandi (oltre 10 MB), escluso .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. Dettagli utili

Promemoria rapido sulla sintassi di glob

  • * — qualsiasi quantità di caratteri (tranne il separatore /)
  • ** — qualsiasi numero di cartelle (in Java funziona)
  • ? — esattamente un carattere qualsiasi
  • [abc] — uno qualunque tra i caratteri a, b, c
  • [a-z] — un carattere dell’intervallo
  • {a,b,c} — uno tra i valori elencati (ad esempio, *.{png,jpg})

Esempi:

  • *.java — tutti i file ".java" nella cartella corrente
  • **/*.java — tutti i file ".java" in tutte le sottocartelle
  • file-??.log — file come "file-01.log", "file-AB.log"
  • [A-Z]*.txt — tutti i file ".txt" che iniziano con una lettera maiuscola

Prestazioni e leak: chiudete DirectoryStream!

Importante!

  • DirectoryStream e gli stream di Files.find/Files.walk sono risorse che vanno chiuse.
  • Usate try-with-resources:
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
    for (Path entry : stream) {
        // ...
    }
}
  • Se non chiudete lo stream, può verificarsi una perdita di risorse (ad esempio, «troppi file aperti»).
  • Per Files.find e Files.walk — chiamate sempre close() oppure usate try-with-resources:
try (Stream<Path> stream = Files.find(...)) {
    stream.forEach(System.out::println);
}

7. Riepilogo ed errori comuni

Errore n. 1: Usare un pattern glob senza capire che * non cerca in modo ricorsivo. Per la ricorsione usate "**/*.java" o Files.walk.

Errore n. 2: Passare il percorso completo a matcher.matches() — di solito bisogna passare solo il nome del file (getFileName()).

Errore n. 3: Dimenticare di chiudere DirectoryStream o Stream<Path> — comporta una perdita di risorse.

Errore n. 4: Pattern regex troppo complessi per un semplice filtraggio — usate glob se è sufficiente.

Errore n. 5: Non escludere le cartelle di servizio (".git", "node_modules") — la ricerca diventa lenta e «si riempie» di file superflui.

1
Compito
JAVA 25 SELF, livello 39, lezione 2
Bloccato
Analista dei dati: selezione dei report critici
Analista dei dati: selezione dei report critici
1
Compito
JAVA 25 SELF, livello 39, lezione 2
Bloccato
Curatore della galleria: inventario delle immagini
Curatore della galleria: inventario delle immagini
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION