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 | |
|
| Ricorsività | |
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.
GO TO FULL VERSION