Java NIO Path-Klasse
Dateisystempfade in modernem Java mit java.nio.file.Path und der Paths-Factory verwalten.
Path ist der moderne Ersatz für java.io.File. Es repräsentiert einen Dateisystempfad — eine geordnete Folge von Namenskomponenten, optional verwurzelt bei / oder C:\ — und nichts weiter. Es liest die Datei nicht. Es prüft nicht, ob die Datei existiert. Es sperrt nichts. Die Operationen auf den Bytes auf der Festplatte befinden sich in Files (dem nächsten Kapitel). Path ist das Substantiv; Files ist das Verb.
Wenn Sie java.io.File verwendet haben, gibt es zwei Unterschiede: Path ist unveränderlich (jede Operation gibt ein neues Path-Objekt zurück), und es trennt klar zwischen „dem Pfad-String" und „was auf der Festplatte an diesem Pfad liegt". Die meisten modernen APIs — Files, FileChannel, BufferedReader.lines()-Überladungen — nehmen Path, nicht File. Neuer Code greift auf Path zurück.
Einen Path erstellen
Path p = Path.of("logs", "2025", "app.log"); // joins components with the platform separator
Path q = Path.of("/etc/hosts"); // absolute Unix path
Path r = Paths.get("C:", "Users", "vaz"); // older factory — same behaviour
Path s = Path.of(URI.create("file:///etc/hosts")); // from a URIPath.of(...) ist die moderne Factory; Paths.get(...) ist die ältere und funktioniert nach wie vor. Beide erzeugen ein Pfad-Objekt ohne das Dateisystem zu berühren. Path.of("nope/nope/nope") gelingt selbst dann, wenn keine solche Datei existiert.
Path.of verbindet Varargs mit dem plattformspezifischen File.separator — / unter Unix, \ unter Windows. Das macht den wörtlichen Pfad-String portabel: Path.of("src", "main", "java") erzeugt auf beiden Plattformen das Richtige. Sobald Sie Path.of("src/main/java") mit fest codierten Schrägstrichen schreiben, haben Sie ihn versehentlich Unix-spezifisch gemacht.
Einen Pfad untersuchen
Die Methoden zum Zugriff auf Komponenten, für Path.of("/var/log/app/today.log"):
| Methode | Gibt zurück | Beispiel |
|---|---|---|
getFileName() | letzte Komponente als Path | today.log |
getParent() | alles außer dem letzten Teil | /var/log/app |
getRoot() | die Wurzel oder null bei relativem Pfad | / |
getNameCount() | Anzahl der Namenskomponenten | 4 |
getName(int i) | i-te Komponente | getName(0) → var |
subpath(b, e) | Namenskomponenten [b, e) | subpath(1, 3) → log/app |
isAbsolute() | ob der Pfad eine Wurzel hat | true |
toString() | der plattformformatierte String | /var/log/app/today.log |
Diese Methoden sind rein: Sie betrachten die interne Namensliste des Pfad-Objekts und geben Ausschnitte davon zurück. Keine von ihnen berührt die Festplatte.
resolve, resolveSibling und die Falle des absoluten Arguments
resolve(other) bedeutet „this und other verbinden":
Path base = Path.of("/var/log");
base.resolve("app.log"); // /var/log/app.log
base.resolve("app/today.log"); // /var/log/app/today.logDie Falle: Wenn other absolut ist, gibt resolve other unverändert zurück:
base.resolve("/etc/hosts"); // /etc/hosts -- base is discarded
base.resolve(Path.of("/etc/hosts")); // same: /etc/hostsDies ist das dokumentierte Verhalten, und es trifft jeden Java-Programmierer mindestens einmal. Wenn Sie einen Dateinamen von einer Benutzereingabe entgegennehmen und ihn gegen ein konfiguriertes Basisverzeichnis auflösen, landet ein Angreifer, der "/etc/passwd" liefert, mit seinem absoluten Pfad — und umgeht die Basis. Validieren oder normalisieren Sie externe Eingaben immer, bevor Sie sie mit resolve verwenden.
resolveSibling(other) ersetzt die letzte Komponente:
Path p = Path.of("/var/log/today.log");
p.resolveSibling("yesterday.log"); // /var/log/yesterday.logEs entspricht getParent().resolve(other) mit integrierter Null-Prüfung. Nützlich für „schreibe die Ausgabe neben die Eingabe".
relativize: das Gegenstück
Gegeben eine Basis und ein Ziel gibt base.relativize(target) den relativen Pfad von base zu target zurück:
Path base = Path.of("/var/log");
Path target = Path.of("/var/log/app/today.log");
base.relativize(target); // app/today.log
target.relativize(base); // ../..Der Vertrag: base.resolve(base.relativize(target)) ist äquivalent zu target (modulo normalize). So wandeln Sie ein absolutes Ziel in eine kurze, relative Referenz innerhalb eines Basisverzeichnisses um — nützlich für Log-Zeilen, Archiv-Einträge und URLs.
Beide Pfade müssen vom gleichen Typ sein (beide absolut oder beide relativ) und müssen aus demselben FileSystem stammen. Eine Vermischung wirft IllegalArgumentException.
normalize: . und .. zusammenfassen
Path-Objekte erlauben .- und ..-Komponenten — Path.of("/var/log/../tmp") ist ein gültiger Path. normalize() entfernt diese syntaktisch:
Path.of("/var/log/../tmp").normalize(); // /var/tmp
Path.of("./a/./b/../c").normalize(); // a/c„Syntaktisch" ist wichtig: normalize arbeitet auf String-Ebene. Es fragt das Dateisystem nicht, ob .. tatsächlich dorthin zeigt, wohin die Strings hindeuten. Wenn /var/log ein symbolischer Link auf /tmp/logs ist, dann ist auf der Festplatte /var/log/.. gleich /tmp, nicht /var. normalize() weiß das nicht — es löscht einfach das ...
Wenn Sie den echten Pfad auf der Festplatte benötigen (Symlinks aufgelöst, .. korrekt interpretiert), verwenden Sie toRealPath(), was einen Dateisystem-Zugriff erfordert:
Path real = Path.of("/var/log/../tmp").toRealPath(); // resolves symlinks, throws if the file doesn't existFür Pfad-Gleichheitsprüfungen und String-Vergleiche ist normalize() das Richtige. Für „den kanonischen Namen der Datei auf der Festplatte gerade jetzt" ist es toRealPath().
Gleichheit ist string-basiert
path1.equals(path2) vergleicht die Pfade als Strings (Komponente für Komponente). Es normalisiert nicht, löst keine Symlinks auf und prüft das Dateisystem nicht:
Path.of("/var/log").equals(Path.of("/var/log/.")); // false -- one has a trailing '.' component
Path.of("/var/log/.").equals(Path.of("/var/log/.").normalize()); // false -- normalize() dropped the '.'
Path.of("/var/log").equals(Path.of("/var/log").normalize()); // true -- already normalized, no change
Path.of("/var/log").equals(Path.of("/var/log")); // trueUm zwei Pfade daraufhin zu vergleichen, ob sie auf dieselbe Datei zeigen, normalisieren Sie beide und vergleichen sie, oder rufen Sie Files.isSameFile(p1, p2) auf (Dateisystem-Zugriff, die einzige vollständig korrekte Prüfung). Für Sortierung und HashSet-Schlüssel ist die String-Gleichheit das, was Path bietet; für die meisten Anwendungsfälle genügt das, aber es entspricht nicht „gleiche Datei auf der Festplatte".
File-Interoperabilität
Path und File lassen sich in beide Richtungen konvertieren:
File f = Path.of("/etc/hosts").toFile();
Path p = new File("/etc/hosts").toPath();Das benötigen Sie, wenn eine alte API File erwartet und eine neue API Path erwartet (oder umgekehrt). Speichern Sie Pfade nicht als File; konvertieren Sie an der API-Grenze und behalten Sie Path in Ihrem Code.
Ein durchgearbeitetes Beispiel: join, resolve, relativize, normalize
Das folgende Programm durchläuft jede in diesem Kapitel eingeführte Path-Operation mit konkreten Pfaden. Die Ausgabe macht den Unterschied zwischen resolve und resolveSibling, zwischen normalize und toRealPath sowie zwischen absolutem und relativem Argument bei resolve sichtbar.
Was man aus dem Durchlauf mitnimmt:
Path.of(\"/var\", \"log\", \"app\", \"today.log\")ergab/var/log/app/today.logunter Unix und\\var\\log\\app\\today.logunter Windows. Die Varargs-Factory die Komponenten verbinden zu lassen ist der portable Weg; fest codierte/oder\im Eingabe-String ist der nicht-portable Weg. Verwenden Sie die Factory.- Die Zeile
resolve(\"/etc/hosts\")hatbaseverworfen und/etc/hostszurückgegeben. Das ist das Verhalten bei absolutem Argument und die häufigste Quelle für „Aber ich habe ein Basisverzeichnis angegeben, warum schreibt es nach/etc/hosts?" Validieren Sie immer vom Benutzer gelieferte Dateinamen, bevor Sieresolveverwenden. Die defensive Form istbase.resolve(other).normalize().startsWith(base)— und selbst das hat subtile Schwachstellen, wenn Symlinks im Spiel sind. base.relativize(target)gabapp/today.logzurück. Diesen Wert mitbase.resolve(...)zu verbinden ergab das ursprüngliche Ziel — die Round-Trip-Identität. Verwenden Sie dies, wenn Sie eine Log-Nachricht oder einen Archiv-Eintrag schreiben, der eine kurze, relative Form eines langen absoluten Pfades benötigt.Path.of(\"/var/log/../tmp/./a/b/../c\").normalize()ergab/var/tmp/a/c. Die Transformation war rein auf String-Ebene: jedes.entfernt, jedesname/..-Paar entfernt. Das Dateisystem wurde nicht befragt.toRealPathin der nächsten Zeile hat das Dateisystem befragt — deshalb war das Ergebnis der kanonische, Symlink-aufgelöste Name der tatsächlichen Datei auf der Festplatte. (Unter macOS sehen Sie, dass der Temp-Pfad unter/private/var/folders/...statt/var/folders/...verwurzelt zurückkommt:toRealPathist dem echten/var→/private/var-Symlink gefolgt, etwas, dasnormalizeniemals tun kann.) DatoRealPathjede Komponente prüft, muss das Zwischenverzeichnissubim Beispiel tatsächlich existieren — deshalb erstellt das Programm es zuerst.- Die beiden
equals-Prüfungen:Path.of(\"/var/log\")undPath.of(\"/var\", \"log\")waren gleich (gleiche interne Namensfolge, gleicher String);Path.of(\"/var/log\")undPath.of(\"/var/log/.\")waren es nicht (eines hat eine abschließende.-Komponente, das andere nicht). Die Erkenntnis:equalsist ein String-Vergleich. Für „zeigen diese zwei Pfade auf dieselbe Datei?" verwenden SieFiles.isSameFile(a, b)aus dem nächsten Kapitel — das ist die einzige Prüfung, die das Dateisystem befragt.
Was kommt als Nächstes
Path ist das Substantiv. Das nächste Kapitel, Java Files-Klasse, behandelt das Verb — Files, die riesige Hilfsklasse mit einzeiligen Operationen auf dem Dateisystem: readString, writeString, createDirectories, copy, move, delete, walk und mehr.