1. Dateien in einem Verzeichnis durchlaufen
Dateiliste erhalten: Files.list und Files.walk
In Java gibt es zwei sehr nützliche Methoden, um mit dem Inhalt eines Ordners zu arbeiten:
- Files.list(Path) – gibt einen Stream (Stream<Path>) aller Dateien und Ordner der obersten Ebene im angegebenen Verzeichnis zurück.
- Files.walk(Path) – gibt einen Stream aller Dateien und Ordner im Verzeichnis und sämtlicher Unterordner zurück (rekursiv).
Beispiel: Ausgabe aller Dateien und Ordner im Verzeichnis data
import java.nio.file.*;
import java.io.IOException;
public class ListFilesExample {
public static void main(String[] args) throws IOException {
Path dir = Paths.get("data");
// Wir erhalten einen Stream aller Dateien und Ordner der obersten Ebene
try (var stream = Files.list(dir)) {
stream.forEach(System.out::println);
}
}
}
Wenn Sie alle Unterordner durchlaufen möchten (rekursiv), verwenden Sie Files.walk:
try (var stream = Files.walk(dir)) {
stream.forEach(System.out::println);
}
Achtung:
Files.walk kann sehr viele Dateien zurückgeben, wenn die Struktur groß ist – starten Sie das nicht im Wurzelverzeichnis des Laufwerks, wenn Sie nicht die „Liste des gesamten Universums“ sehen wollen!
Visualisierung: Unterschied zwischen list und walk
| Methode | Was zurückgegeben wird |
|---|---|
|
Nur den Inhalt des aktuellen Ordners |
|
Alle Dateien und Ordner, einschließlich der in Unterordnern |
2. Filterung von Dateien
Einer der größten Vorteile bei der Arbeit mit Streams (Stream<Path>) ist die Möglichkeit, Dateien nach beliebigen Kriterien zu filtern: Erweiterung, Name, Größe, Erstellungsdatum usw.
Filterung nach Erweiterung
Angenommen, wir benötigen nur .txt-Dateien. So können wir sie filtern:
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.forEach(System.out::println);
}
Filterung nach Namen
Wir möchten alle Dateien, deren Name mit "report" beginnt:
stream
.filter(path -> path.getFileName().toString().startsWith("report"))
.forEach(System.out::println);
Filterung nach Größe
Erhalten wir nur „schwere“ Dateien (größer als 1 MB):
stream
.filter(path -> {
try {
return Files.size(path) > 1_000_000;
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
Filterung nach Datum
Zum Beispiel Dateien, die in den letzten 7 Tagen geändert wurden:
import java.time.*;
import java.nio.file.attribute.*;
stream
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isAfter(Instant.now().minus(Duration.ofDays(7)));
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
Alles zusammen: kombinierte Filterung
Filter lassen sich „wie im Restaurant“ kombinieren: zum Beispiel alle .log-Dateien, die älter als einen Monat und größer als 10 KB sind:
stream
.filter(path -> path.toString().endsWith(".log"))
.filter(path -> {
try {
return Files.size(path) > 10_000;
} catch (IOException e) {
return false;
}
})
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isBefore(Instant.now().minus(Duration.ofDays(30)));
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
3. Massenweises Kopieren und Löschen von Dateien
Massenoperationen sind einfach: Man erhält einen Stream von Dateien und wendet dann die gewünschte Funktion auf jedes Element an. Wichtig ist, mögliche Fehler nicht zu vergessen (z. B. Datei ist belegt oder existiert bereits).
Kopieren aller Dateien von einem Ordner in einen anderen
import java.nio.file.StandardCopyOption;
Path sourceDir = Paths.get("data");
Path targetDir = Paths.get("backup");
try (var stream = Files.list(sourceDir)) {
stream.forEach(path -> {
Path targetPath = targetDir.resolve(path.getFileName());
try {
Files.copy(path, targetPath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Kopiert: " + path.getFileName());
} catch (IOException e) {
System.out.println("Fehler beim Kopieren " + path.getFileName() + ": " + e.getMessage());
}
});
}
Löschen aller temporären Dateien (*.tmp)
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".tmp"))
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Gelöscht: " + path.getFileName());
} catch (IOException e) {
System.out.println("Fehler beim Löschen " + path.getFileName() + ": " + e.getMessage());
}
});
}
Verwendung der Stream-API zur Verarbeitung von Pfadkollektionen
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.sorted((p1, p2) -> {
try {
return Long.compare(Files.size(p2), Files.size(p1)); // nach Größe, absteigend
} catch (IOException e) {
return 0;
}
})
.limit(5) // nur die 5 größten
.forEach(System.out::println);
}
4. Praxisaufgaben
Suchen und Löschen von Dateien, die älter als ein bestimmtes Datum sind
Löschen wir alle Dateien, die seit mehr als einem Jahr nicht geändert wurden:
try (var stream = Files.list(dir)) {
stream
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isBefore(Instant.now().minus(Duration.ofDays(365)));
} catch (IOException e) {
return false;
}
})
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Gelöscht: " + path.getFileName());
} catch (IOException e) {
System.out.println("Fehler beim Löschen " + path.getFileName() + ": " + e.getMessage());
}
});
}
Massenweises Umbenennen von Dateien nach Muster
Angenommen, wir müssen jeder .txt-Datei das Präfix "old_" voranstellen:
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.forEach(path -> {
Path newPath = path.resolveSibling("old_" + path.getFileName());
try {
Files.move(path, newPath);
System.out.println("Umbenannt: " + path.getFileName() + " -> " + newPath.getFileName());
} catch (IOException e) {
System.out.println("Fehler beim Umbenennen " + path.getFileName() + ": " + e.getMessage());
}
});
}
Kopieren aller Dateien aus allen Unterordnern (rekursiv)
Wir verwenden Files.walk, um alle Dateien in allen Unterordnern zu durchlaufen:
try (var stream = Files.walk(sourceDir)) {
stream
.filter(Files::isRegularFile)
.forEach(path -> {
Path relative = sourceDir.relativize(path);
Path targetPath = targetDir.resolve(relative);
try {
Files.createDirectories(targetPath.getParent());
Files.copy(path, targetPath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Kopiert: " + path + " -> " + targetPath);
} catch (IOException e) {
System.out.println("Fehler beim Kopieren " + path + ": " + e.getMessage());
}
});
}
5. Wichtige Punkte und Besonderheiten
Fehlerbehandlung bei Massenoperationen
Bei Massenoperationen trifft man fast immer auf „problematische“ Dateien: kein Zugriff, Datei belegt, Pfad zu lang, Datei existiert bereits usw. Wenn Ausnahmen nicht innerhalb der Schleife behandelt werden, kann das Programm beim ersten Fehler „abstürzen“. Verwenden Sie daher stets try-catch innerhalb von forEach, um den Fehler bei einer einzelnen Datei zu „schlucken“, die übrigen aber trotzdem zu verarbeiten.
Auswirkungen der Rekursion auf Leistung und Aufrufstack
Bei der Verwendung von Files.walk für große Verzeichnisse kann ein sehr langer Durchlauf entstehen, wenn man die Rekursion manuell implementiert. Zum Glück ist die Methode selbst effizient umgesetzt und führt nicht zu einem Stacküberlauf. Wenn Sie jedoch Ihre eigene rekursive Funktion zum Löschen/Kopieren von Verzeichnissen schreiben, seien Sie vorsichtig: Bei enorm tiefen Strukturen kann der Stack „platzen“.
Arbeiten mit großen Ordnern
- Verwenden Sie Files.walk mit Vorsicht: Wenn ein Ordner Tausende von Dateien enthält, kann das Zeit kosten und viel Speicher „fressen“.
- Wenn Sie nur die oberste Ebene verarbeiten müssen – verwenden Sie Files.list.
- Zum Suchen nach Dateien per Muster verwenden Sie eine Namensfilterung (endsWith, matches usw.).
Beispiel: „Ordner von temporären Dateien bereinigen“
Path tempDir = Paths.get("data/tmp");
try (var stream = Files.walk(tempDir)) {
stream
.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".tmp"))
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Gelöscht: " + path);
} catch (IOException e) {
System.out.println("Fehler beim Löschen " + path + ": " + e.getMessage());
}
});
}
Wesentliche Methoden für Massenoperationen
| Operation | Methode/Vorgehen | Besonderheiten |
|---|---|---|
| Alle Dateien erhalten | |
walk – rekursiv, list – nur oberste Ebene |
| Filterung nach Erweiterung | |
Kann mit anderen Filtern kombiniert werden |
| Gruppe von Dateien kopieren | |
Existenz des Zielordners prüfen |
| Gruppe von Dateien löschen | |
Für jede Operation besser try-catch verwenden |
| Massenweises Umbenennen | |
Umbenennen ist ein move mit neuem Namen |
6. Typische Fehler bei Massenoperationen
Fehler Nr. 1: Nicht behandelte Ausnahmen innerhalb von forEach.
Ein sehr häufiges Problem: Wenn man jede Operation nicht in try-catch kapselt, „stürzt“ das Programm bereits bei der ersten problematischen Datei ab, und der Rest wird nicht verarbeitet.
Fehler Nr. 2: Versuch, einen Ordner als Datei zu löschen/kopieren (oder umgekehrt).
Die Methoden Files.delete und Files.copy behandeln Dateien und Ordner unterschiedlich. Verwechseln Sie sie nicht! Ein Versuch, einen nicht leeren Ordner mit der Standardmethode zu löschen, führt beispielsweise zu einem Fehler.
Fehler Nr. 3: Falsche Bildung des Zielpfads beim Kopieren.
Wenn der Zielpfad ohne Berücksichtigung der Struktur gebildet wird (z. B. ohne relativize), kann man Dateien überschreiben oder eine falsche Struktur im Archiv erhalten.
Fehler Nr. 4: Zu viele Dateien gleichzeitig öffnen.
Wenn Sie Lese-/Schreibstreams innerhalb einer Schleife öffnen, vergessen Sie nicht, sie zu schließen! Besser try-with-resources verwenden.
Fehler Nr. 5: Rekursives Durchlaufen ohne Tiefenbegrenzung.
Bei der Verwendung von Files.walk für sehr große und tiefe Ordner kann es zu Leistungs- und Speicherproblemen kommen. Wenn Sie die Tiefe begrenzen müssen – verwenden Sie die Überladung mit Tiefenparameter: Files.walk(dir, depth).
Fehler Nr. 6: Implizites Ignorieren von versteckten/Systemdateien.
Wenn die Anwendung mit Benutzerdaten arbeitet, kann man versehentlich versteckte oder Systemdateien erwischen. Um sie zu filtern, verwenden Sie die Methode Files.isHidden(path).
GO TO FULL VERSION