W3docs

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 URI

Path.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"):

MethodeGibt zurückBeispiel
getFileName()letzte Komponente als Pathtoday.log
getParent()alles außer dem letzten Teil/var/log/app
getRoot()die Wurzel oder null bei relativem Pfad/
getNameCount()Anzahl der Namenskomponenten4
getName(int i)i-te KomponentegetName(0)var
subpath(b, e)Namenskomponenten [b, e)subpath(1, 3)log/app
isAbsolute()ob der Pfad eine Wurzel hattrue
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.log

Die 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/hosts

Dies 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.log

Es 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 exist

Fü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"));              // true

Um 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.

java— editable, runs on the server

Was man aus dem Durchlauf mitnimmt:

  • Path.of(\"/var\", \"log\", \"app\", \"today.log\") ergab /var/log/app/today.log unter Unix und \\var\\log\\app\\today.log unter 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\") hat base verworfen und /etc/hosts zurü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 Sie resolve verwenden. Die defensive Form ist base.resolve(other).normalize().startsWith(base) — und selbst das hat subtile Schwachstellen, wenn Symlinks im Spiel sind.
  • base.relativize(target) gab app/today.log zurück. Diesen Wert mit base.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, jedes name/..-Paar entfernt. Das Dateisystem wurde nicht befragt. toRealPath in 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: toRealPath ist dem echten /var/private/var-Symlink gefolgt, etwas, das normalize niemals tun kann.) Da toRealPath jede Komponente prüft, muss das Zwischenverzeichnis sub im Beispiel tatsächlich existieren — deshalb erstellt das Programm es zuerst.
  • Die beiden equals-Prüfungen: Path.of(\"/var/log\") und Path.of(\"/var\", \"log\") waren gleich (gleiche interne Namensfolge, gleicher String); Path.of(\"/var/log\") und Path.of(\"/var/log/.\") waren es nicht (eines hat eine abschließende .-Komponente, das andere nicht). Die Erkenntnis: equals ist ein String-Vergleich. Für „zeigen diese zwei Pfade auf dieselbe Datei?" verwenden Sie Files.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.

Übungen

Übung
`base` ist `Path.of('/srv/uploads')` und `userInput` ist der String `'/etc/passwd'` (ein vom Angreifer kontrollierter Wert). Ihr Code führt `base.resolve(userInput)` aus, um den Zielpfad zu berechnen. Was ist der resultierende Pfad, und was ist die Sicherheitslektion?
`base` ist `Path.of('/srv/uploads')` und `userInput` ist der String `'/etc/passwd'` (ein vom Angreifer kontrollierter Wert). Ihr Code führt `base.resolve(userInput)` aus, um den Zielpfad zu berechnen. Was ist der resultierende Pfad, und was ist die Sicherheitslektion?
Was this page helpful?