Datei- und Verzeichnisnamen konstruieren in Java - Path, File, Files - Feature-Bild

Dateien in Java, Teil 3: Datei- und Verzeichnisnamen konstruieren (mit File, Path und Paths)

Nach dem Lesen und Schreiben von Dateien in Java geht es in diesem dritten Teil der Artikelserie darum, wie man die Klassen File, Path und Paths verwendet, um Datei- und Verzeichnispfade zu konstruieren – und zwar betriebssystemunabhängig.

Wenn du bereits mit Dateioperationen in Java zu tun hattest, hast du diese Klassen wahrscheinlich schon verwendet, um mit new File(), Paths.get() oder Path.of() einen Dateinamen an eine der Schreib- oder Lesemethoden zu übergeben.

Viel tiefer steigen die meisten Programmierer in diese Klassen nicht ein. Dies liegt unter anderem daran, dass diese Klassen auch für erfahrene Java-Programmierer verwirrend sein können: Was ist der Unterschied zwischen File.getName() und File.getPath()? Worin unterscheiden sich File.getAbsolutePath(), File.getCanonicalPath() und Path.normalize()? Was ist der Unterschied zwischen Paths.get() und Path.of()?

Folgende Fragen werden in diesem Artikel beantwortet:

  • Was ist der Unterschied zwischen Dateiname, Verzeichnisname und Pfad?
  • Wie konstruiert man einen relativen Verzeichnis- bzw. Dateipfad unabhängig vom Betriebssystem?
  • Wie konstruiert man einen absoluten Verzeichnis- bzw. Dateipfad unabhängig vom Betriebssystem?
  • Was änderte sich mit der Einführung der NIO.2 File API?
  • Was genau liefern die File-Methoden getName(), getPath(), getParent(), getParentFile(), getAbsolutePath(), getAbsoluteFile(), getCanonicalPath(), getCanonicalFile() zurück?
  • Was liefern die Path-Methoden getFileName(), getName(int index), getParent(), getRoot(), toAbsolutePath() und normalize()?
  • Wie werden mit Path.resolve() und Path.resolveSibling() Path-Objekte verknüpft?
  • Wann verwendet man den nun File und wann Path? Und kann ich die beiden ineinander konvertieren?

Contents

Grundlagen: Begriffsdefinitionen, Betriebssystemunabhängigkeit, NIO.2

Wie unterscheiden sich Dateiname, Verzeichnisname, relativer Pfad und absoluter Pfad?

Bevor wir beginnen müssen wir uns über die Terminologie einig werden. Oft werden beispielsweise die Bezeichnungen „Pfad“ und „Verzeichnis“ durcheinandergebracht.

  • Dateiname: der Name einer Datei ohne Verzeichnisangabe und Trennzeichen, z. B. readme.txt
  • Verzeichnisname: der Name eines einzelnen Verzeichnisses ohne Elternverzeichnis(se), z. B. apache-maven-3.6.3 oder log
  • Pfad: der „Weg“ zu einem Objekt des Dateisystems, also zu Dateien und Verzeichnissen. Dieser kann absolut oder relativ sein:
    • Ein absoluter Pfad ist immer eindeutig und unabhängig von der aktuellen Position im Dateisystem.
    • Ein relativer Pfad bezieht sich auf die aktuelle Position innerhalb des Dateisystems. Er beschreibt, wie man von dieser zum Ziel kommt. Liegt das Ziel im aktuellen Verzeichnis, dann entspricht der relative Pfad für gewöhnlich dem Datei- bzw. Verzeichnisnamen.

Die folgende Tabelle zeigt einige Beispiele für absolute und relative Pfade zu Verzeichnissen und Dateien – sowohl für Windows als auch für Linux/Mac:

Linux / MacWindows
Absoluter Pfad zu einem Verzeichnis:/var/logC:\Windows
Absoluter Pfad zu einer Datei:/var/log/syslogC:\Windows\explorer.exe
Relativer Pfad zu einem Verzeichnis:../../var/log

(von /home/user zu /var/log)
..\Windows

(von C:\Users zu C:\Windows)
Relativer Pfad zu einer Datei:../../var/log/syslog

(von /home/user zu /var/log/syslog)
..\Windows\explorer.exe

(von C:\Users zu C:\Windows\explorer.exe)

Betriebssystemunabhängige Pfad- und Verzeichnisnamen

Wie in der Tabelle gesehen, beginnen absolute Pfade unter Windows mit einem Laufwerksbuchstaben und einem Doppelpunkt, Verzeichnisse werden durch einen Backslash (‚\‘) getrennt. Unter Linux und Mac beginnen absolute Pfade mit einem Schrägstrich bzw. Forward Slash (‚/‘), und durch selbigen werden auch Verzeichnisse separiert.

Über die Konstante File.separator oder die Methode FileSystems.getDefault().getSeparator() kann man auf das Trennzeichen des aktuell verwendeten Betriebssystems zugreifen und somit Pfadnamen „manuell“ (also durch das Verketten von Strings) erzeugen. Beispielsweise so:

String homePath = System.getProperty("user.home");
String fileName = "test" + System.currentTimeMillis();
String filePath = homePath + File.separator + fileName;
System.out.println("filePath = " + filePath);

Je nach Betriebssystem bekommen wir eine unterschiedliche Ausgabe:

  • Windows: filePath = C:\Users\svenw\test1578950760671
  • Linux: filePath = /home/sven/test1578950836130

Beim home-Verzeichnis funktioniert das noch ganz gut, beim temp-Verzeichnis hingegen (dies bekommen wir über die System Property „java.io.tmpdir“) erhalten wir folgende Ausgaben:

  • Windows: filePath = C:\Users\svenw\AppData\Local\Temp\\test1578950862590
  • Linux: filePath = /tmp/test1578950884314

Hast du das Problem entdeckt?

Unter Windows endet der Pfad für das temporäre Verzeichnis bereits mit einem Backslash. Durch das Hinzufügen eines Separators erzeugt unser Code einen doppelten Backslash im Dateipfad.

Wir könnten nun prüfen, ob der Verzeichnisname bereits mit einem Separator endet und diesen nur hinzufügen, wenn er nicht vorhanden ist. Das ist jedoch gar nicht nötig. Man kann Datei- und Verzeichnisnamen wesentlich eleganter konstruieren – komplett ohne String-Operationen, und zwar mit Hilfe der Klassen java.io.File und (ab Java 7) java.nio.file.Path.

„Alte“ Java File API vs. NIO.2 File API

In Java 7 wurde mit dem JSR 203 die „NIO.2 File API“ eingeführt (NIO steht dabei für „New I/O“). Diese stellt eine ganze Reihe neuer Klassen zum Umgang mit Dateien zur Verfügung (vorgestellt in den vorangegangenen Artikel über das Schreiben von Dateien und das Lesen von Dateien).

Dateien und Verzeichnisse wurden zuvor über die Klasse java.io.File repräsentiert. Dies führt insbesondere bei Anfängern zu Verwirrung, da der Klassenname eben darauf schließen lässt, dass diese nur Dateien repräsentiert, keine Verzeichnisse.

In NIO.2 übernimmt diese Aufgabe die – nun passender benannte – Klasse java.nio.file.Path, deren Schnittstelle im Vergleich zu java.io.File komplett neugeschrieben wurde.

Datei- und Verzeichnispfaden konstruieren mit java.io.File

Beginnen wir mit der „alten“ Klasse, java.io.File. Ein File-Objekt kann einen Dateinamen, einen Verzeichnisnamen, einen relativen Datei- oder Verzeichnispfad oder einen absoluten Datei- oder Verzeichnispfad darstellen (wobei ein Datei-/Verzeichnisname eigentlich auch ein relativer Datei-/Verzeichnispfad ist, relativ zu demjenigen Verzeichnis, in dem sich die Datei/das Verzeichnis befindet).

java.io.File: Datei- und Verzeichnisnamen

Dateiname mit java.io.File darstellen

Einen Dateinamen (ohne Angabe eines Verzeichnisses) definierst du bspw. wie folgt (wir bleiben beim oben verwendeten Muster „test<Zeitstempel>“:

File file = new File("test" + System.currentTimeMillis());

Du kannst nun u. a. folgende Informationen aus dem File-Objekt auslesen:

MethodeRückgabewert
file.getName()test1578953190701
file.getPath()test1578953190701
file.getParent() / file.getParentFile()null
file.getAbsolutePath() / file.getAbsoluteFile()/happycoders/git/filedemo/test1578953190701

Die Methode getName() liefert den Dateinamen zurück, den wir dem Konstruktor übergeben haben. getPath() liefert den Pfad, der in diesem Fall dem Dateinamen entspricht, da wir kein Verzeichnis angegeben haben. Aus dem gleichen Grund liefern getParent() und getParentFile() jeweils null zurück, wobei die erste Methode einen String zurückliefert, die zweite ein entsprechendes File-Objekt.

Mit den Methoden getAbsolutPath() und getAbsolutFile() setzt du diese Datei sozusagen in das aktuelle Arbeitsverzeichnis und erhälst den kompletten Pfad der Datei einschließlich Verzeichnis- und Dateiname. Auch diese zwei Methoden unterscheiden sich nur dadurch, dass die erste einen String zurückliefert und die zweite ein entsprechendes File-Objekt.

Es gibt noch die Methoden getCanonicalPath() und getCanonicalFile(), welche in diesem und den folgenden Beispielen dieselben Werte zurückliefern würden wie getAbsolutePath() und getAbsoluteFile(). Wir sehen in einem späteren Beispiel, in welchem Fall diese auch andere Werte enthalten können.

Verzeichnisname mit java.io.File darstellen

Das im vorangegangenen Abschnitt konstruierte File-Objekt könnte statt einer Datei ebensogut ein Verzeichnis mit demselben Namen repräsentieren. Die in der Tabelle oben aufgelisteten Methoden würden dabei dieselben Ergebnisse liefern.

Eine Unterscheidung wäre nur dann möglich, falls bereits eine Datei oder ein Verzeichnis mit diesem Namen existiert. Falls eine entsprechende Datei existiert, würde die Methode file.isFile() den Wert true zurückliefern. Existiert hingegen ein Verzeichnis mit diesem Namen, würde die Methode file.isDirectory() den Wert true liefern. Existieren weder Datei noch Verzeichnis, geben beide Methoden false zurück. Je nach weiterer Verwendung kann das File-Objekt dann entweder zum Anlegen eines Verzeichnis oder zum Erstellen einer Datei verwendet werden.

java.io.File: relative Datei- und Verzeichnispfade

Relativer Dateipfad mit java.io.File

Um ein Verzeichnis mit anzugeben, können wir dieses dem File-Konstruktor als Parameter übergeben – in der einfachsten Form als String. Mit folgendem Code legst du die Testdatei in ein tests-Verzeichnis:

File file = new File("tests", "test" + System.currentTimeMillis());

Die Getter liefern nun folgende Informationen über das File-Objekt (die Unterschiede zum vorherigen Beispiel sind fett hervorgehoben):

MethodeRückgabewert
file.getName()test1578953190701
file.getPath()tests/test1578953190701
file.getParent() / file.getParentFile()tests
file.getAbsolutePath() / file.getAbsoluteFile()/happycoders/git/filedemo/tests/test1578953190701

Jetzt sehen wir einen Unterschied zwischen getName() und getPath(): die erste Methode liefert nur den Dateinamen ohne die Verzeichnisangabe, die zweite Methode liefert den kompletten relativen Pfad. getParent() und getParentFile() (zur Erinnerung: die erste Methode liefert einen String, die zweite ein entsprechendes File-Objekt) liefern nun das angegebene Verzeichnis tests zurück. In den absoluten Pfadangaben, die von getAbsolutePath() und getAbsoluteFile() zurückgegeben werden (auch hier: String vs. File), ist entsprechend das Unterverzeichnis tests eingefügt.

Das Verzeichnis kann anstatt als String auch als File-Objekt übergeben werden:

File directory = new File("tests");
File file = new File(directory, "test" + System.currentTimeMillis());

Relativer Dateipfad mit verschachtelten Verzeichnissen

Es können auch mehrere Verzeichnis-Ebenen verschachtelt werden:

File testsDirectory = new File("tests");
File yearDirectory = new File(testsDirectory, "2020");
File dayDirectory = new File(yearDirectory, "2020-01-13");
File file = new File(dayDirectory, "test" + System.currentTimeMillis());

Somit können wir beliebig tiefe Verzeichnispfade konstruieren, ohne auch nur ein einziges Mal das Separatorzeichen verwenden zu müssen. Das Beispiel konstruiert ein File-Objekt mit dem Pfad tests/2020/2020-01-13/test1578953190701.

Was wird hier die Methode getParent() zurückliefern? tests/2020/2020-01-13 oder nur 2020-01-13? Probieren wir es aus…

MethodeRückgabewert
file.getName()test1578953190701
file.getPath()tests/2020/2020-01-13/test1578953190701
file.getParent() / file.getParentFile()tests/2020/2020-01-13
file.getAbsolutePath() / file.getAbsoluteFile()/happycoders/git/filedemo/tests/2020/2020-01-13/test1578953190701

Der Parent repräsentiert also den Pfad zum Elternverzeichnis, nicht nur dessen Namen. Auf den Namen des Elternverzeichnisses können wir über file.getParentFile().getName() zugreifen.

Relativer Verzeichnispfad mit java.io.File

Auch in den Beispielen aus dem vorangegangenen Abschnitt könnte test1578953190701 statt einer Datei ein Verzeichnis sein. Der Pfad lässt hierauf keine Rückschlüsse zu.

java.io.File: absolute Datei- und Verzeichnispfade

Absoluter Verzeichnispfad mit java.io.File

Achtung: Die absoluten Pfade betrachten wir in umgekehrter Reihenfolge: Wir konstruieren zuerst den Verzeichnispfad und dann den Dateipfad, da wir ohne absoluten Verzeichnispfad als Parent keinen absoluten Dateipfad generieren können.

(Es gibt eine Ausnahme, die wir in den bisheringen Beispielen bereits kennengelernt haben: Den absoluten Dateipfad für eine Datei im aktuellen Verzeichnis erhalten wir, in dem wir den File-Konstruktor mit nur dem Dateinamen aufrufen und auf dem erzeugten File-Objekt dann die Methode getAbsoluteFile() / getAbsolutePath() aufrufen.)

Folgende Möglichkeiten haben wir, um einen absoluten Verzeichnispfad zu konstruieren:

  • aus einem als String dargestellten (und damit betriebssystemabhängigen) absoluten Verzeichnispfad
  • aus den zu Beginn erwähnten System Properties wie „user.home“ und „java.io.tmpdir“
  • aus dem aktuellen Verzeichnis

Absoluten Verzeichnispfad aus einem String erzeugen

Haben wir den absoluten Verzeichnispfad in einem String (beispielsweise aus einer Konfigurationsdatei), können wir diesen direkt an den File-Konstruktor übergeben. Folgendes Beispiel verwendet der Einfachheit halber eine String-Konstante:

File directory = new File("/var/log/myapp");

Für dieses absolute Verzeichnis liefern die Getter des File-Objekts folgende Rückgabewerte:

MethodeRückgabewert
file.getName()myapp
file.getPath()/var/log/myapp
file.getParent() / file.getParentFile()/var/log
file.getAbsolutePath() / file.getAbsoluteFile() /var/log/myapp

Die Methoden getPath(), getAbsolutePath() und getAbsoluteFile() liefern nun alle den absoluten Pfad des Verzeichnisses zurück. getParent() und getParentFile() liefern den absoluten Pfad des Elternverzeichnisses.

Absoluten Verzeichnispfad aus System-Properties erzeugen

Über die System-Properties user.home bzw. java.io.tmpdir bekommt man – betriebssystemunabhängig – das Home-Verzeichnis des Users bzw. das temporäre Verzeichnis.

Die Methode System.getProperty() liefert letztendlich den Pfad auch als String zurück, den wir dann wiederum dem File-Konstruktor übergeben können. Oben haben wir gesehen, dass unter Windows das temporäre Verzeichnis einen abschließenden Backslash hat, das Home-Verzeichnis nicht. Macht uns das an dieser Stelle Probleme?

Mit folgendem Code können wir es unter Windows testen:

String homeDir = System.getProperty("user.home");
System.out.println("homeDir                = " + homeDir);
System.out.println("homeDir as File object = " + new File(homeDir));

String tempDir = System.getProperty("java.io.tmpdir");
System.out.println("tempDir                = " + tempDir);
System.out.println("tempDir as File object = " + new File(tempDir));

Das Programm gibt folgendes aus:

homeDir                = C:\Users\svenw
homeDir as File object = C:\Users\svenw
tempDir                = C:\Users\svenw\AppData\Local\Temp\
tempDir as File object = C:\Users\svenw\AppData\Local\Temp

Der überflüssige abschließende Backslash wurde entfernt, wir müssen uns also um nichts weiter kümmern.

Absoluten Verzeichnispfad aus aktuellem Verzeichnis erzeugen

Mit dem bisherigen Wissen könnten wir wie folgt den absoluten Verzeichnispfad des aktuellen Verzeichnisses konstruieren:

File file = new File("dummy");
File absoluteFile = file.getAbsoluteFile();
File absoluteDirectory = absoluteFile.getParentFile();

Wir haben hier einen Dummy-Dateipfad generiert, für diesen den absoluten Pfad konstruiert (wodurch die Datei in das aktuelle Verzeichnis gesetzt wird) und daraus dann wiederum den Parent extrahiert – also den absoluten Pfad des Verzeichnisses, in dem die Datei liegt.

Das ist zugegebenermaßen ziemlich umständlich. Glücklicherweise gibt es eine elegantere Möglichkeit: Das aktuelle Verzeichnis kann auch über eine System-Property, user.dir, ausgelesen und dann dem File-Konstruktor übergeben werden:

File currentDir = new File(System.getProperty("user.dir"));

Absoluter Dateipfad mit java.io.File

Nachdem wir verschiedene Varianten betrachtet haben, wie ein absoluter Verzeichnispfad erstellt werden kann, können wir daraus nun einen absoluten Dateipfad konstruieren. Dazu müssen wir lediglich dem File-Konstruktor Verzeichnis und Dateiname übergeben.

File tempDir = new File(System.getProperty("java.io.tmpdir"));
File subDir = new File(tempDir, "myapp");
String fileName = "foo";
File file = new File(subDir, fileName);

Für dieses Beispiel liefern die Getter des File-Objekts folgende Rückgabewerte:

MethodeRückgabewert
file.getName()foo
file.getPath()/tmp/myapp/foo
file.getParent() / file.getParentFile()/tmp/myapp
file.getAbsolutePath() / file.getAbsoluteFile()/tmp/myapp/foo

Wir sehen ein ähnliches Muster wie bei dem Beispiel mit dem absoluten Verzeichnispfad /var/log/myapp: Die Methoden getPath(), getAbsolutePath() und getAbsoluteFile() liefern den absoluten Pfad der Datei zurück. Und getParent() und getParentFile() liefern den absoluten Pfad des Elternverzeichnisses.

Achtung:

Wenn du ein File-Objekt für eine Datei im aktuellen Verzeichnis erstellst, macht es einen Unterschied, ob du das Objekt mit dem aktellen Verzeichnis und dem Dateinamen erzeugst oder nur mit dem Dateinamen. Die Methoden getAbsoluteFile() / getAbsolutePath() liefern zwar in beiden Fällen den absoluten Dateipfad zurück. Allerdings liefert getPath() nur im ersten Fall den absoluten Pfad zurück, im zweiten lediglich den Dateinamen. Und getParent() und getParentFile() liefern nur im ersten Fall das Elternverzeichnis zurück, im zweiten hingegen null.

java.io.File: Übersicht der File-Getter-Methoden

Die folgende Tabelle zeigt zusammengefasst, was die File-Getter abhängig vom repräsentierten Dateisystem-Objekt jeweils zurückliefern:

MethodeDatei-/Verzeichnisnamerelativer Datei-/Verzeichnispfadabsoluter Datei-/Verzeichnispfad
getName()Datei-/VerzeichnisnameDatei-/VerzeichnisnameDatei-/Verzeichnisname
getPath()Datei-/Verzeichnisnamerelativer Datei-/Verzeichnispfadabsoluter Datei-/Verzeichnispfad
getParent() / getParentFile()null relativer Pfad zum Elternverzeichnisabsoluter Pfad zum Elternverzeichnis
getAbsolutePath() / getAbsoluteFile()Absoluter Pfad aus Kombination von aktuellem Verzeichnis und Datei-/VerzeichnisnameAbsoluter Pfad aus Kombination von aktuellem Verzeichnis und relativem Datei-/Verzeichnispfadabsoluter Datei-/Verzeichnispfad

Noch einmal zur Erinnerung: getParent() und getAbsolutePath() liefern einen String; getParentFile() und getAbsoluteFile() ein entsprechendes File-Objekt.

java.io.File: Was ist der Unterschied zwischen getCanonicalPath() / getCanonicalFile() und getAbsolutePath() / getAbsolutePath()?

In allen bisherigen Beispielen hätten die Methoden getCanonicalPath() und getCanonicalFile() das gleiche Ergebnis geliefert wie getAbsolutePath() und getAbsoluteFile() – nämlich den jeweiligen absoluten Pfad (jeweils als String bzw. File-Objekt).

Was also ist der Unterschied?

Ein „canonical path“ ist eindeutig, d. h. es gibt nur einen einzigen solchen Pfad zu einer Datei. Absolute Pfade hingegen kann es zu ein- und derselben Datei mehrere geben. Ein Beispiel:

Für die Datei /var/log/syslog ist eben dieser String auch der „canonical path“. Derselbe String ist auch ein absoluter Pfad. Es gibt allerdings noch weitere absolute Pfade, wie beispielsweise:

  • /var/log/./syslog,
  • /var/log/../log/syslog,
  • /home/user/../../var/log/syslog,
  • sowie alle Pfade, die über symbolischen Links letztendlich zu /var/log/syslog zeigen.

Konstruktion von Datei- und Verzeichnispfaden mit java.nio.file.Path und Paths

Obwohl die Schnittstelle von java.nio.file.Path gegenüber java.io.File komplett geändert wurde, sind die dahinter liegenden Konzepte unverändert geblieben. Nach wie vor können Dateinamen, Verzeichnisnamen, relative Datei- und Verzeichnispfade sowie absolute Datei- und Verzeichnispfad repräsentiert werden.

Was wurde geändert? In den folgenden Abschnitten seht ihr, wie Path-Objekte konstruiert werden – und zwar in der gleichen Struktur und anhand der gleichen Beispiele wie zuvor die File-Objekte.

Auf die Unterscheidung in Datei- und Verzeichnisnamen verzichte ich im Folgenden. Wir haben oben bereits festgestellt, dass, solange das entsprechende Dateisystem-Objekt noch nicht existiert, aus dessen Pfad nicht erkennbar ist, ob es sich um eine Datei oder ein Verzeichnis handelt.

java.nio.file.Path: Datei- und Verzeichnisnamen

Statt eines Konstruktors verwenden wir bei java.nio.file.Path eine Factory-Methode, um Instanzen zu erzeugen. Diese befand sich ursprünglich in der Klasse java.nio.file.Paths (mit „s“ am Ende), sie wurde in Java 11 dann auch direkt in die Path-Klasse mit aufgenommen. Ein Path-Objekt für den Dateinamen „test<Zeitstempel>“ erstellen wir wie folgt:

Path path = Paths.get("test" + System.currentTimeMillis());

Ab Java 11 kannst du statt Paths.get() auch Path.of() verwenden. Einen Unterschied gibt es nicht. Intern ruft Paths.get() die neuere Methode Path.of() auf und diese wiederum FileSystems.getDefault().getPath().

 Path path = Path.of("test" + System.currentTimeMillis()); 

Folgende Methoden liefern Informationen analog zu den zuvor gezeigten Methoden der File-Klasse:

MethodeRückgabewert
path.getFileName()test1579037366379
path.getNameCount()1
path.getName(0)test1579037366379
path.getParent()null
path.getRoot()null
path.toAbsolutePath()/happycoders/git/filedemo/test1579037366379
path.normalize() test1579037366379

Zunächst einmal: Alle gezeigten Methoden – mit Ausnahme von getNameCount() – liefern wiederum ein Path-Objekt zurück. Es gibt keine Varianten dieser Methoden, die einen String zurückliefern. Die einzige Möglichkeit ein Path-Objekt in einen String umzuwandeln, ist dessen toString()-Methode aufzurufen.

Die Methode getFileName() liefert den Datei- bzw. Verzeichnisnamen zurück. getNameCount() ist bei einem Dateinamen ohne Verzeichnisangabe, bzw. bei Verzeichnissen ohne Elternverzeichnis immer 1. Und der erste Name, abrufbar durch getName(0), ist ebenfalls der Datei-/Verzeichnisname. getParent() liefert – wie die gleichnamige File-Methode – null zurück. Die Methode getRoot() liefert ebenfalls null, da ein einzelner Dateiname immer relativ ist. Mit toAbsolutePath() setzen wir – analog zu File.getAbsolutePath() – die Datei/das Verzeichnis ins aktuelle Vezeichnis und erhalten den entsprechenden absoluten Pfad.

Path.normalize() ist vergleichbar mit File.getCanonicalPath() mit dem Unterschied, dass bei der Normalisierung eines Pfades relative Pfade relativ bleiben, während getCanonicalPath() sowohl für absolute als auch für relative File-Objekte grundsätzlich einen absoluten Pfad generiert.

java.nio.file.Path: relative Datei- und Verzeichnispfade

Um Verzeichnisse mit anzugeben, gibt es bei Path zwei Möglichkeiten:

  • du kannst alle Verzeichnisse in der Factory-Methode Paths.get() bzw. Path.of() auflisten;
  • du kannst ein existierendes Path-Objekt mit dessen resolve()-Methode in ein neues Path-Objekt überführen, wobei die resolve()-Methode dem Wechseln des Verzeichnisses mit dem Windows- bzw. Linux-Kommando cd entspricht.

Relative Datei- und Verzeichnispfade mit Paths.get() / Path.of() konstruieren

Den Factory-Methoden kannst du beliebig viele Verzeichnisnamen übergeben. Den relativen Pfad tests/test1578953190701 bspw. würdest du wie folgt konstruieren (der Zeitstempel ist hier der Einfachheit halber hartkodiert):

Path path = Paths.get("tests", "test1578953190701");

Die Getter dieses Path-Objekts liefern folgende Resultate (die Unterschiede zum einzelnen Dateinamen habe ich wieder fett hervorgehoben):

MethodeRückgabewert
path.getFileName()test1578953190701
path.getNameCount()2
path.getName(0)tests
path.getName(1)test1578953190701
path.getParent() tests
path.getRoot()null
path.toAbsolutePath()/happycoders/git/filedemo/tests/test1578953190701
path.normalize()tests/test1578953190701

Durch das Verzeichnis hat sich der nameCount von 1 auf 2 erhöht. In dem name-Array wurde an Position 0 der Verzeichnisname eingefügt, der Dateiname ist auf Position 1 gerutscht. getParent() liefert ebenfalls den Verzeichnisnamen zurück. Ebenso sind im absoluten Pfad sowie in der normalisierten Version der Verzeichnisname enthalten.

Verschachtelte Datei- und Verzeichnispfade mit Paths.get() / Path.of() konstruieren

Soll die Datei im Verzeichnis tests/2020/2020-01-13 liegen, wird die Factory-Methode wie folgt aufgerufen:

Path path = Paths.get("tests", "2020", "2020-01-13", "test1578953190701");

Hier auch dafür noch einmal die Getter-Resultate:

MethodeRückgabewert
path.getFileName()test1578953190701
path.getNameCount()4
path.getName(0)tests
path.getName(1)2020
path.getName(2)2020-01-13
path.getName(3)test1578953190701
path.getParent()tests/2020/2020-01-13
path.getRoot()null
path.toAbsolutePath()/happycoders/git/filedemo/tests/2020/2020-01-13/test1578953190701
path.normalize()tests/2020/2020-01-13/test1578953190701

Der nameCount ist entsprechend erhöht und das name-Array enthält alle Verzeichnisse des Pfades sowie den Dateinamen. Auch hier liefert getParent() den kompletten bekannten Pfad zum Verzeichnis zurück, nicht nur dessen Namen.

Relative Datei- und Verzeichnispfade mit Path.resolve() konstruieren

Eine alternative Möglichkeit, um das Path-Objekt für den Pfad tests/2020/2020-01-13/test1578953190701 zu konstruieren ist die Nutzung der resolve()-Methode. Diese kombiniert den Pfad, auf der die Methode aufgerufen wird, mit dem ihr übergebenen Pfad:

Path testsDir = Paths.get("tests");
Path yearDir = testsDir.resolve("2020");
Path dayDir = yearDir.resolve("2020-01-13");
Path path = dayDir.resolve("test1578953190701");

Die resolve()-Verknüpfung ist assoziativ, d. h. die einzelnen Teile können auch in anderer Reihenfolge verbunden werden, z. B. so:

Path testsDir = Paths.get("tests"); // tests
Path yearDir = testsDir.resolve("2020"); // tests/2020

Path dayDir = Paths.get("2020-01-13"); // 2020-01-13
Path fileInDayDir = dayDir.resolve("test1578953190701"); // 2020-01-13/test1578953190701

Path path = yearDir.resolve(fileInDayDir); // tests/2020/2020-01-13/test1578953190701

Shortcut: Path.resolveSibling()

Nehmen wir an, wir haben das Path-Objekt, dass wir im vorherigen Abschnitt konstruiert haben und möchten im gleichen Verzeichnis eine weitere Datei anlegen. Wenn wir das Path-Objekt, dass das Verzeichnis repräsentiert, noch zur Verfügung haben, können wir darauf resolve() mit dem neuen Dateinamen aufrufen. Andernfalls könnten wir mit getParent() auf das Verzeichnis zugreifen und dann resolve() aufrufen:

Path sibling = path.getParent().resolve("test" + System.currentTimeMillis());

Genau für diesen Zweck gibt es die Abkürzung resolveSibling(), die einem immerhin fünf Tastaturanschläge und Bytes einspart:

Path sibling = path.resolveSibling("test" + System.currentTimeMillis());

java.nio.file.Path: absolute Datei- und Verzeichnispfade

Analog zu java.io.File können wir einen absoluten Pfad auch bei java.nio.file.Path aus einem String erzeugen, den wir aus einer Konfigurationsdatei oder aus einer System Property lesen. Diesen können wir, so wie er ist, der Factory-Methode übergeben. Wir brauchen ihn nicht erst aufzusplitten. Am Beispiel des Verzeichnisses /var/log/myapp:

Path path = Paths.get("/var/log/myapp");

Natürlich können wir auch einen absoluten Pfad mit der resolve()-Methode verändern. Folgendes Beispiel entspricht dem Beispiel des absoluten Dateipfads mit java.io.File:

Path tempDir = Path.of(System.getProperty("java.io.tmpdir"));
Path subDir = tempDir.resolve("myapp");
String fileName = "foo";
Path file = subDir.resolve(fileName);

Für diesen absoluten Pfad liefern die Getter-Methoden:

MethodeRückgabewert
path.getFileName()foo
path.getNameCount()3
path.getName(0)tmp
path.getName(1)myapp
path.getName(2)foo
path.getParent()/tmp/myapp
path.getRoot()/
path.toAbsolutePath()/tmp/myapp/foo
path.normalize()/tmp/myapp/foo

Hier erleben wir zum ersten Mal, dass path.getRoot() nicht null zurückliefert, sondern „/“, das Linux-Rootverzeichnis. Unter Windows erhalten wir hier „C:\“ (es sei denn das temporäre Verzeichnis liegt in einem anderen Dateisystem). Das Root-Verzeichnis ist nicht Teil des name-Arrays.

java.nio.file.Path: Übersicht der Getter-Methoden

Hier siehst du noch einmal zusammengefasst, was die Path-Getter jeweils zurückliefern:

MethodeDatei-/Verzeichnisnamerelativer Datei-/Verzeichnispfadabsoluter Datei-/Verzeichnispfad
path.getFileName()Datei-/VerzeichnisnameDatei-/VerzeichnisnameDatei-/Verzeichnisname
path.getNameCount()1Anzahl der Verzeichnisse + DateiAnzahl der Verzeichnisse + Datei
path.getName(index)Datei-/VerzeichnisnameDatei-/Verzeichnisnamen an der gegebenen Position (0-basiert)Datei-/Verzeichnisnamen an der gegebenen Position (0-basiert, Root zählt nicht mit)
path.getParent()nullrelativer Pfad zum Elternverzeichnisabsoluter Pfad zum Elternverzeichnis
path.getRoot()nullnullRoot des Dateisystems, z. B. „/“ oder „C:\“
path.toAbsolutePath()Absoluter Pfad aus Kombination von aktuellem Verzeichnis und Datei-/VerzeichnisnameAbsoluter Pfad aus Kombination von aktuellem Verzeichnis und relativem Datei-/Verzeichnispfadabsoluter Datei-/Verzeichnispfad
path.normalize()normalisierter Datei-/Verzeichnisnamenormalisierter relativer Datei-/Verzeichnisnamenormalisierter absoluter Datei-/Verzeichnisname

Zusammenfassung und Ausblick

Dieser Artikel gab einen ausführlichen Überblick darüber, wie man mit Hilfe der Klassen File, Path und Paths Datei- und Verzeichnispfade betriebssystemunabhängig konstruiert.

Meine Empfehlung ist es ab Java 7 ausschließlich die NIO.2-Klassen Path und Paths zu verwenden, bzw. ab Java 11 nur noch Path. Wenn du für eine bestimmte Datei-Operation ein File-Objekt benötigst, kannst du dir jederzeit über Path.toFile() eines erzeugen lassen.

In den folgenden Teilen der Serie wird es um folgende Themen gehen:

Im späteren Verlauf kommen wir zu fortgeschrittenen Themen:

  • Die in Java 1.4 eingeführten NIO-Channels und Buffer, um insbesondere das Arbeiten mit großen Dateien zu beschleunigen
  • File Locking, um parallel – also aus mehreren Threads oder Prozessen – konfliktfrei auf dieselben Dateien zuzugreifen
  • Memory-mapped I/O für rasend schnellen Dateizugriff ohne Streams

Hast du Fragen, Anregungen, Verbesserungsvorschläge? Dann freue ich mich über einen Kommentar. Kennst du andere, die das Thema interessant finden? Dann freue ich mich, wenn du den Artikel über einen der Buttons unten teilst. Möchtest du informiert werden, wenn der nächste Teil erscheint? Dann melde dich gerne über das folgende Formular zu meinem Newsletter an.

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.