CodeGym /Kurse /JAVA 25 SELF /NIO2: Files, Paths, Files.walk: Durchlaufen des Dateisyst...

NIO2: Files, Paths, Files.walk: Durchlaufen des Dateisystems

JAVA 25 SELF
Level 39 , Lektion 0
Verfügbar

1. NIO2: genauer kennenlernen

Wir haben NIO2 bereits kennengelernt, aber jetzt wiederholen wir und vertiefen unser Wissen über diese nützliche Bibliothek für die Arbeit mit Dateien und Verzeichnissen.

Früher gab es in Java nur die Klasse File. Sie konnte die Existenz einer Datei prüfen, Dateien und Ordner erstellen und löschen sowie die Dateiliste in einem Verzeichnis abrufen. Aber sie hatte viele Einschränkungen:

  • Arbeiten mit Pfaden ist umständlich, insbesondere wenn unterschiedliche Betriebssysteme berücksichtigt werden müssen (C:\Users\user\file.txt unter Windows und /home/user/file.txt unter Linux);
  • es fehlt eine vernünftige Unterstützung für symbolische Links, Berechtigungen und Dateiattribute;
  • die Möglichkeiten zum Durchlaufen des Verzeichnisbaums sind begrenzt;
  • die Fehlerbehandlung ließ zu wünschen übrig.

Mit dem Erscheinen von NIO2 (New Input/Output, Version 2) in Java 7 wurde das Leben der Entwickler einfacher und angenehmer. Jetzt stehen zur Verfügung:

  • die Klasse Path für die komfortable Arbeit mit Pfaden zu Dateien und Ordnern;
  • die Klasse Files, die alle grundlegenden Operationen bietet: Lesen, Schreiben, Kopieren, Löschen, Abrufen von Informationen über Dateien;
  • das Interface FileVisitor und Methoden wie Files.walk, die ein einfaches und flexibles Durchlaufen des Dateisystems ermöglichen.

Warum ist das wichtig?

  • Plattformunabhängigkeit: Der gleiche Code läuft unter Windows, Linux und macOS, ohne über Trennzeichen (/ oder \) nachdenken zu müssen.
  • Sicherheit und Komfort: Mehr Informationen bei Fehlern, weniger Magie und weniger böse Überraschungen.
  • Leistungsfähigkeit: Sehr große Verzeichnisse lassen sich verarbeiten und sogar rekursiv mit Filterung und paralleler Verarbeitung durchlaufen.

2. Zentrale Klassen: Path und Files

Klasse Path

Path — ist die moderne Repräsentation eines Pfads zu einer Datei oder einem Ordner. Er zeigt nicht zwingend auf eine tatsächlich existierende Datei — es ist einfach ein Pfad, mit dem sich bequem arbeiten lässt.

Einen Path erhalten

import java.nio.file.Path;
import java.nio.file.Paths;

Path path1 = Paths.get("file.txt"); // relativer Pfad
Path path2 = Paths.get("/home/user/file.txt"); // absoluter Pfad
Path path3 = Path.of("mydir", "subdir", "file.txt"); // ab Java 11+

Fakt: Path ist betriebssystemunabhängig. Vergessen Sie das manuelle Aneinanderfügen von Strings mit / oder \!

In String umwandeln

System.out.println(path1.toString());

Elternverzeichnis und Dateinamen ermitteln

Path parent = path1.getParent(); // kann bei relativen Pfaden null sein
Path fileName = path1.getFileName(); // nur der Dateiname

Klasse Files

Files ist eine Sammlung statischer Methoden für alle Datei- und Verzeichnisoperationen:

  • Existenzprüfung: Files.exists(path)
  • Dateien lesen und schreiben: Files.readAllBytes(path), Files.write(path, bytes)
  • Informationen abrufen: Files.size(path), Files.getLastModifiedTime(path)
  • Kopieren, Löschen, Verschieben: Files.copy, Files.delete, Files.move

Beispiele:

import java.nio.file.Files;
import java.nio.file.Path;

Path path = Path.of("file.txt");
if (Files.exists(path)) {
    System.out.println("Datei existiert!");
    System.out.println("Größe: " + Files.size(path) + " Byte");
    System.out.println("Letzte Änderung: " + Files.getLastModifiedTime(path));
} else {
    System.out.println("Datei nicht gefunden.");
}

3. Das Dateisystem durchlaufen: Files.walk und Co.

Problem des alten Ansatzes

Im alten API musste man, um alle Dateien in einem Ordner und dessen Unterordnern zu durchlaufen, rekursive Funktionen schreiben, manuell prüfen, was Datei und was Ordner ist, und darauf achten, nicht in eine Endlosrekursion zu geraten. Das war nicht nur mühsam, sondern auch sehr fehleranfällig.

Moderner Ansatz: Files.walk

Files.walk(Path start) gibt einen Stream<Path> zurück — einen Stream aller Dateien und Ordner ab dem angegebenen Pfad, einschließlich aller Unterverzeichnisse. Das Durchlaufen des Dateisystems ist jetzt einfach Arbeit mit Streams!

Beispiel: Alle Dateien und Ordner ausgeben

import java.nio.file.*;

try (var paths = Files.walk(Path.of("mydir"))) {
    paths.forEach(System.out::println);
}

Hier werden alle Pfade ausgegeben: sowohl Dateien als auch Ordner, beginnend bei mydir.

Beispiel: Nur Dateien (ohne Ordner)

try (var paths = Files.walk(Path.of("mydir"))) {
    paths.filter(Files::isRegularFile)
         .forEach(System.out::println);
}

Die Methode Files.isRegularFile(path) gibt true nur für reguläre Dateien zurück (keine Ordner, keine Symlinks).

Beispiel: Dateien nach Erweiterung suchen

Angenommen, wir müssen alle .txt-Dateien im Verzeichnis und in Unterverzeichnissen finden:

try (var paths = Files.walk(Path.of("mydir"))) {
    paths.filter(Files::isRegularFile)
         .filter(path -> path.toString().endsWith(".txt"))
         .forEach(System.out::println);
}

Beispiel: Gesamtgröße aller Dateien berechnen

long totalSize = 0;
try (var paths = Files.walk(Path.of("mydir"))) {
    totalSize = paths.filter(Files::isRegularFile)
                     .mapToLong(path -> {
                         try {
                             return Files.size(path);
                         } catch (Exception e) {
                             System.err.println("Fehler beim Lesen der Größe: " + path);
                             return 0L;
                         }
                     })
                     .sum();
}
System.out.println("Gesamtgröße der Dateien: " + totalSize + " Byte");

Wichtig!

  • Die Methode Files.walk gibt einen Stream zurück, der geschlossen werden muss (sie implementiert AutoCloseable). Deshalb verwenden wir try-with-resources!
  • Standardmäßig wird bis ganz nach unten in die Tiefe gegangen (alle Unterverzeichnisse). Die Tiefe kann begrenzt werden: Files.walk(path, maxDepth)

4. Praxisaufgaben

Aufgabe 1: Alle Bilder im Verzeichnis finden

Es sollen alle Dateien mit den Endungen .jpg, .png, .gif im Ordner images gefunden und ihre Namen ausgegeben werden.

import java.nio.file.*;
import java.util.Set;

Set<String> extensions = Set.of(".jpg", ".png", ".gif");

try (var paths = Files.walk(Path.of("images"))) {
    paths.filter(Files::isRegularFile)
         .filter(path -> {
             String name = path.getFileName().toString().toLowerCase();
             return extensions.stream().anyMatch(name::endsWith);
         })
         .forEach(System.out::println);
}

Aufgabe 2: Alle .txt-Dateien in einen anderen Ordner kopieren

import java.nio.file.*;

Path sourceDir = Path.of("src");
Path destDir = Path.of("dest");

try (var paths = Files.walk(sourceDir)) {
    paths.filter(Files::isRegularFile)
         .filter(path -> path.toString().endsWith(".txt"))
         .forEach(path -> {
             try {
                 Path relative = sourceDir.relativize(path);
                 Path target = destDir.resolve(relative);
                 Files.createDirectories(target.getParent());
                 Files.copy(path, target, StandardCopyOption.REPLACE_EXISTING);
                 System.out.println("Kopiert: " + path + " -> " + target);
             } catch (Exception e) {
                 System.err.println("Kopierfehler: " + path);
             }
         });
}

Hier behalten wir die Struktur der Unterverzeichnisse bei.

5. Nützliche Details

Vorteile von NIO2

Plattformunabhängigkeit

Path kümmert sich selbst um die Ordnertrennzeichen. Ihr Code wird unter Windows, Linux und macOS gleichermaßen funktionieren.

Stream-Verarbeitung

Methoden wie Files.walk geben einen Stream (Stream<Path>) zurück, den Sie filtern, transformieren, in Kollektionen sammeln können — alles, was das Stream-API kann.

Arbeiten mit großen Verzeichnissen

Das alte API konnte „abstürzen“, wenn es zu viele Dateien gab (z. B. 100 000 Fotos). NIO2 bewältigt solche Fälle problemlos, da nicht alles auf einmal in den Speicher geladen wird.

Unterstützung von Symlinks, Attributen und Berechtigungen

Man kann feststellen, ob ein Pfad ein symbolischer Link ist (Files.isSymbolicLink(path)), Berechtigungen abrufen (Files.getPosixFilePermissions(path)), den Besitzer der Datei ermitteln und vieles mehr.

Vergleich von altem und neuem API

Operation Altes API (File) Neues API (Path, Files)
Existenz prüfen
file.exists()
Files.exists(path)
Größe ermitteln
file.length()
Files.size(path)
Dateiliste im Ordner
file.listFiles()
Files.list(path)
Rekursives Durchlaufen Rekursion von Hand
Files.walk(path)
Datei kopieren file.renameTo() (problematisch)
Files.copy(src, dest)
Erweiterung ermitteln String parsen
path.getFileName().toString()
Elternverzeichnis ermitteln
file.getParentFile()
path.getParent()
Mit Berechtigungen arbeiten So gut wie gar nicht
Files.getPosixFilePermissions(path)

Wichtige Besonderheiten

Dateityp prüfen

  • Files.isRegularFile(path) — reguläre Datei
  • Files.isDirectory(path) — Verzeichnis
  • Files.isSymbolicLink(path) — Symlink

Arbeiten mit großen Verzeichnissen

  • Sammeln Sie nicht alle Pfade in einer Liste: Arbeiten Sie mit Streams (Stream<Path>) und verarbeiten Sie sie fortlaufend.
  • Nach Abschluss wird der Stream unbedingt geschlossen (try-with-resources).

Ausnahmen

  • Fast alle Methoden können eine IOException werfen — denken Sie daran, Fehler zu behandeln (oder weiterzureichen).

Durchlauftiefe begrenzen

try (var paths = Files.walk(Path.of("mydir"), 2)) { // nur 2 Ebenen
    // ...
}

6. Typische Fehler beim Arbeiten mit NIO2

Fehler Nr. 1: Stream von walk vergessen zu schließen. Wenn Sie try-with-resources nicht verwenden, kann es zu Ressourcenlecks kommen — der Dateisystem-Stream bleibt geöffnet. Verwenden Sie stets die Konstruktion try (var paths = Files.walk(...)) { ... }.

Fehler Nr. 2: Nicht geprüft, ob der Pfad ein Verzeichnis ist. Wenn Sie an Files.walk einen Pfad zu einer Datei statt zu einem Ordner übergeben, kann es zu unerwartetem Verhalten oder einem Fehler kommen.

Fehler Nr. 3: Ausnahmen nicht behandelt. Praktisch alle Methoden von NIO2 können eine IOException werfen. Ignorieren Sie diese Fehler nicht — geben Sie zumindest eine Meldung für den Nutzer aus oder loggen Sie sie.

Fehler Nr. 4: Verwechslung bei Pfad-Trennzeichen. Wenn Sie Pfade manuell mit / oder \ zusammenbauen, ist das unnötig! Verwenden Sie Path.of(...) oder resolve(...) — diese Methoden regeln das betriebssystemabhängig korrekt.

Fehler Nr. 5: Versuch, ein riesiges Verzeichnis „in den Speicher“ zu laden. Sammeln Sie nicht alle Pfade in einer Liste, wenn es sehr viele Dateien gibt — arbeiten Sie mit Streams (Stream<Path>) und verarbeiten Sie sie fortlaufend.

Fehler Nr. 6: Plattformunabhängigkeit vergessen. Hardcoden Sie keine absoluten Pfade im Windows- oder Unix-Stil. Verwenden Sie Path sowie Operationen wie resolve/relativize — das funktioniert auf jedem OS korrekt.

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