1. Einführung
Wenn Sie mit Dateien und Ordnern arbeiten, müssen Sie oft nur bestimmte Dateien auswählen: zum Beispiel alle ".java"-Dateien, alle Bilder, alle Logs für ein bestimmtes Datum. Dafür verwendet man in Java (und nicht nur dort) Globbing (glob) und reguläre Ausdrücke (regex).
- Globbing — eine einfache Art, Dateinamenmuster mit speziellen Zeichen zu beschreiben (*, ?, [], {}), wie in der Kommandozeile von Linux oder Windows.
- Regex — eine mächtige Sprache für reguläre Ausdrücke, geeignet für komplexe Muster.
Beispiele für Globbing-Muster
- *.java — alle Dateien mit der Erweiterung ".java" im aktuellen Ordner.
- **/*.java — alle ".java"-Dateien in allen Unterordnern (doppelter Stern — rekursive Suche).
- *.{png,jpg} — alle Dateien mit der Erweiterung ".png" oder ".jpg".
- file-??.log — Dateien wie "file-01.log", "file-AB.log" (zwei beliebige Zeichen).
- [A-Z]*.txt — alle ".txt"-Dateien, die mit einem Großbuchstaben beginnen.
Globbing ist einfacher als regex und reicht für die Dateifilterung meist aus.
Vergleich von glob und regex
| Eigenschaft | glob | regex |
|---|---|---|
| Einfachheit | Sehr einfach | Komplexer, aber mächtiger |
| Zeichen | |
Alle Möglichkeiten von regex |
| Beispiele | |
|
| Rekursion | |
Keine eingebaute Unterstützung |
| Wann verwenden | Dateifilterung | Komplexe Prüfungen |
2. PathMatcher: Dateifilterung per Muster
In Java NIO (java.nio.file) verwendet man für die Filterung per Muster das Interface PathMatcher. Man erhält es über FileSystems.getDefault().getPathMatcher(...).
Syntax
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.java");
- "glob:*.java" — Globbing-Muster.
- "regex:.*\\.java" — regulärer Ausdruck.
Beispiel: Dateien in einem Verzeichnis filtern
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);
}
}
}
Wichtig:
- matcher.matches() prüft in der Regel nur den Dateinamen — übergeben Sie entry.getFileName() und nicht den vollständigen Pfad.
- Für die rekursive Suche verwenden Sie Files.walk() oder Files.find().
Files.walk() — eine NIO2-Methode, die rekursiv einen Stream<Path> mit allen Dateien und Ordnern im angegebenen Verzeichnis inklusive Unterordnern zurückgibt. Im Gegensatz zu DirectoryStream, das nur den Inhalt eines einzelnen Ordners zeigt, erlaubt Files.walk() die Arbeit mit dem Verzeichnisbaum über die Stream-API.
Beispiel:
import java.nio.file.*;
import java.util.stream.Stream;
Path start = Paths.get("src");
try (Stream<Path> stream = Files.walk(start)) { // rekursiv alle Unterordner
stream.filter(Files::isRegularFile)
.forEach(System.out::println);
}
Beispiel mit regex
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("regex:.*\\.(png|jpg)");
3. Files.newDirectoryStream: Filtern beim Auflisten eines Verzeichnisses
Die Methode Files.newDirectoryStream() ermöglicht es, Dateien direkt per Muster oder mit einem eigenen Filter zu filtern.
Filtern per Glob-Muster
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
for (Path entry : stream) {
System.out.println(entry);
}
}
Der zweite Parameter ist ein Glob-Muster (ohne Präfix "glob:").
Filtern mit DirectoryStream.Filter
Wenn Sie komplexere Logik brauchen (z. B. Filtern nach Größe, Datum, Ausschluss von Ordnern), verwenden Sie DirectoryStream.Filter<Path>:
DirectoryStream.Filter<Path> filter = path -> {
// Beispiel: nur Dateien, keine Ordner, und weder .git noch 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: leistungsstarke Suche mit BiPredicate
Wenn Sie Dateien nach komplexen Bedingungen suchen müssen (z. B. nach Datum, Größe, Name, rekursiv), verwenden Sie Files.find.
Syntax
Stream<Path> stream = Files.find(
startDir, // wo gesucht wird
maxDepth, // Tiefe (Integer.MAX_VALUE — rekursiv)
(path, attrs) -> {
// path — Pfad zur Datei
// attrs — Dateiattribute (Größe, Datum usw.)
return path.getFileName().toString().endsWith(".log")
&& attrs.size() > 1024; // nur große Logdateien
}
);
stream.forEach(System.out::println);
- Der zweite Parameter ist die maximale Suchtiefe.
- Der dritte — BiPredicate<Path, BasicFileAttributes>: gibt true zurück, wenn die Datei passt.
Beispiel: Ordner .git und node_modules ausschließen
Stream<Path> stream = Files.find(
Paths.get("."),
Integer.MAX_VALUE,
(path, attrs) -> {
String name = path.getFileName().toString();
// Ordner .git und node_modules ausschließen
if (name.equals(".git") || name.equals("node_modules")) return false;
// Nur .log-Dateien größer als 1 MB
return name.endsWith(".log") && attrs.size() > 1024 * 1024;
}
);
stream.forEach(System.out::println);
5. Praxis
Beispiel 1: Alle .log-Dateien finden, außer .git und node_modules
Files.walk(Paths.get("."))
.filter(path -> {
String name = path.getFileName().toString();
// Ordner ausschließen
if (name.equals(".git") || name.equals("node_modules")) return false;
// Nur .log-Dateien
return name.endsWith(".log");
})
.forEach(System.out::println);
Beispiel 2: Alle Bilder finden, die nach einem bestimmten Datum erstellt wurden
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);
Beispiel 3: Alle großen Dateien (größer als 10 MB) finden, außer .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. Nützliche Details
Kurze Übersicht zur Glob-Syntax
- * — beliebig viele beliebige Zeichen (außer dem Trenner /)
- ** — beliebig viele beliebige Ordner (funktioniert in Java)
- ? — genau ein beliebiges Zeichen
- [abc] — eines der Zeichen a, b, c
- [a-z] — ein Zeichen aus dem Bereich
- {a,b,c} — einer der aufgeführten Werte (zum Beispiel *.{png,jpg})
Beispiele:
- *.java — alle ".java"-Dateien im aktuellen Ordner
- **/*.java — alle ".java"-Dateien in allen Unterordnern
- file-??.log — Dateien wie "file-01.log", "file-AB.log"
- [A-Z]*.txt — alle ".txt"-Dateien, die mit einem Großbuchstaben beginnen
Performance und Lecks: Schließen Sie DirectoryStream!
Wichtig!
- DirectoryStream und Streams aus Files.find/Files.walk sind Ressourcen, die geschlossen werden müssen.
- Verwenden Sie try-with-resources:
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.java")) {
for (Path entry : stream) {
// ...
}
}
- Wenn Sie den Stream nicht schließen, kann es zu Ressourcenlecks kommen (z. B. „Zu viele geöffnete Dateien“).
- Bei Files.find und Files.walk gilt: unbedingt close() aufrufen oder try-with-resources verwenden:
try (Stream<Path> stream = Files.find(...)) {
stream.forEach(System.out::println);
}
7. Fazit und typische Fehler
Fehler Nr. 1: Ein Glob-Muster verwenden, ohne zu beachten, dass * nicht rekursiv sucht. Für Rekursion verwenden Sie "**/*.java" oder Files.walk.
Fehler Nr. 2: Den vollständigen Pfad an matcher.matches() übergeben — in der Regel sollte nur der Dateiname (getFileName()) übergeben werden.
Fehler Nr. 3: DirectoryStream oder Stream<Path> nicht geschlossen — führt zu Ressourcenlecks.
Fehler Nr. 4: Zu komplexe regex-Muster für einfache Filter — verwenden Sie glob, wenn es ausreicht.
Fehler Nr. 5: Systemordner (".git", "node_modules") nicht ausgeschlossen — die Suche wird langsam und mit unnötigen Dateien „zugemüllt“.
GO TO FULL VERSION