W3docs

Dateien in Java erstellen

Dateien und Verzeichnisse in Java erstellen mit File.createNewFile, Files.createFile und Files.createDirectories.

„Eine neue leere Datei anlegen" und „diesen Verzeichnisbaum erstellen" sind zwei der kleinsten Operationen, die das Dateisystem bietet — dennoch stellt Java vier überschneidende Wege bereit, um jede davon auszuführen. Die Unterschiede sind wichtig: Sie entscheiden darüber, ob eine Methode „fehlschlägt, wenn die Datei existiert" oder „lautlos überschreibt", ob sie wie mkdir oder mkdir -p arbeitet, ob sie ein boolean zurückgibt (Legacy) oder eine Exception wirft (modern).

Dieses Kapitel behandelt die vier Erstellungsmethoden:

  • File.createNewFile() — Legacy-Dateierststellung.
  • File.mkdir() / File.mkdirs() — Legacy-Verzeichniserstellung.
  • Files.createFile(path) — modernes atomares „Erstellen oder Fehler."
  • Files.createDirectory(path) / Files.createDirectories(path) — moderne Verzeichniserstellung.

Außerdem die Hilfsmethoden für temporäre Dateien (Files.createTempFile, Files.createTempDirectory) und die OpenOption-Flags, mit denen Writer Dateien implizit erstellen können.

File.createNewFile() — Legacy, gibt ein boolean zurück

File f = new File("data/users.txt");
boolean created = f.createNewFile();      // true if it actually created the file
                                          // false if it already existed
                                          // throws IOException if the parent dir is missing

Der Vertrag lautet atomares Check-and-Create: Das Betriebssystem garantiert, dass kein anderer Prozess denselben Pfad zwischen der Existenzprüfung und der Erstellung anlegen kann. Das macht createNewFile zu einem primitiven Lock in manchen Legacy-„PID-Datei"-Idiomen — if (!f.createNewFile()) throw new IllegalStateException("already running");

Was es nicht tut:

  • Es erstellt keine fehlenden übergeordneten Verzeichnisse. new File("does/not/exist/file.txt").createNewFile() wirft eine IOException.
  • Es gibt false zurück (wirft keine Exception), wenn die Datei bereits existiert.

Wenn es nur wichtig ist, dass die Datei am Ende des Aufrufs vorhanden ist, ist der false-Rückgabewert in Ordnung. Wenn die Datei brandneu sein muss (Lock-Semantik), ist false das Signal, einen anderen Pfad einzuschlagen.

File.mkdir() und File.mkdirs()

new File("logs").mkdir();           // creates "logs" — fails if "." has no perms or parent missing
new File("a/b/c").mkdirs();          // creates "a", "a/b", and "a/b/c" — like `mkdir -p`

Beide geben ein boolean zurück und verlieren dabei Informationen darüber, warum ein Fehler aufgetreten ist. mkdir schlägt fehl, wenn ein übergeordnetes Verzeichnis fehlt; mkdirs nicht. Beide geben true zurück (nur wenn das Verzeichnis neu erstellt wurde) — wenn es bereits existiert, geben sie false zurück. Zusammen mit dem fehlenden Fehlerinformations-Problem ist das genau der Typ von Legacy-API, der in einen Helfer eingewickelt wird:

File dir = new File("data");
if (!dir.exists() && !dir.mkdirs()) throw new IOException("cannot create " + dir);

Das moderne Files.createDirectories(path) ist der Einzeiler-Ersatz dafür.

Files.createFile(path) — modern, wirft Exceptions

Files.createFile ist das java.nio.file-Gegenstück zu File.createNewFile() — mit einem wichtigen Unterschied: Es wirft Exceptions statt ein boolean zurückzugeben.

Path p = Path.of("data/users.txt");
Files.createFile(p);                          // creates an empty regular file
                                              // throws FileAlreadyExistsException if it exists
                                              // throws NoSuchFileException if the parent is missing

FileAlreadyExistsException ist das, was man catcht, wenn das Existenzergebnis relevant ist; NoSuchFileException ist das, was man catcht (oder durch createDirectories verhindert), wenn das übergeordnete Verzeichnis möglicherweise fehlt. Die Exception-Typen sind spezifische Unterklassen von IOException, sodass ein pauschales catch (IOException e) weiterhin funktioniert.

Man kann FileAttribute-Argumente übergeben, um beim Erstellen POSIX-Berechtigungen auf Unix zu setzen — der häufigste Anwendungsfall ist, sicherzustellen, dass geheime Dateien (private Schlüssel, Tokens) mit 0600 erstellt werden:

import static java.nio.file.attribute.PosixFilePermissions.*;
var attr = asFileAttribute(fromString("rw-------"));
Files.createFile(Path.of("/tmp/secret"), attr);   // born with 0600 permissions, atomically

(Dieser Aufruf wirft UnsupportedOperationException unter Windows, das kein POSIX-Berechtigungsmodell hat — bei plattformübergreifender Unterstützung mit einer Plattformprüfung absichern.)

Files.createDirectory versus Files.createDirectories

Derselbe Unterschied wie zwischen mkdir und mkdir -p, nur Exception-basiert:

Files.createDirectory(Path.of("logs"));          // one level deep; parent must exist
Files.createDirectories(Path.of("a/b/c"));        // creates every missing ancestor

createDirectory wirft FileAlreadyExistsException, wenn das Ziel bereits existiert und kein Verzeichnis ist; wenn es bereits ein Verzeichnis ist, wirft es ebenfalls (was normalerweise nicht erwünscht ist).

createDirectories ist die benutzerfreundlichere Wahl: Es tut nichts, wenn jedes Verzeichnis bereits vorhanden ist, und erstellt andernfalls alles Fehlende. Es wirft keine Exception, wenn der Pfad bereits als Verzeichnis existiert. Das macht es idempotent — sicher beim Start aufrufbar, ohne vorher exists() zu prüfen.

Temporäre Dateien und Verzeichnisse

Für Tests, Scratch-Space und „Ich brauche kurz einen sicheren Ablageort" liefert das JDK Files.createTempFile und Files.createTempDirectory:

Path scratch = Files.createTempFile("session-", ".log");          // /tmp/session-3829387.log
Path workdir = Files.createTempDirectory("export-");               // /tmp/export-1827392

Beide wählen einen eindeutigen Namen im System-Temp-Verzeichnis, beide geben einen Path zum neuen Eintrag zurück, und beide erstellen den Eintrag unter Unix mit restriktiven Berechtigungen. Präfix und Suffix sind Hinweise, denen das JDK einen eindeutigen Wert anfügt — der genaue Name lässt sich nicht wählen (das ist der Sinn: ein anderer Aufrufer kann ihn nicht vorhersagen und überschreiben).

Temporäre Dateien werden nicht automatisch gelöscht. Entweder:

  • Man ruft Files.deleteIfExists(path) auf, wenn man fertig ist; oder
  • Man ruft path.toFile().deleteOnExit() auf, um eine Löschung beim JVM-Shutdown einzuplanen (wird bei harten Kills nicht ausgeführt); oder
  • Man öffnet die Datei mit StandardOpenOption.DELETE_ON_CLOSE, wenn sie nur benötigt wird, während ein Stream geöffnet ist.

Writer erstellen Dateien implizit

Meistens ist ein expliziter „Datei erstellen"-Aufruf gar nicht nötig — ein Writer erstellt die Datei automatisch. Files.newBufferedWriter, Files.write und Files.writeString akzeptieren alle OpenOption... varargs, die bestimmen, was passiert, wenn die Datei existiert oder nicht:

import static java.nio.file.StandardOpenOption.*;
Files.writeString(path, "hello\n", CREATE, WRITE, TRUNCATE_EXISTING);
Files.writeString(path, "more\n",  CREATE, WRITE, APPEND);
Files.writeString(path, "new\n",   CREATE_NEW);     // fails if file exists
  • CREATE — erstellt, wenn fehlend; andernfalls die vorhandene Datei öffnen.
  • CREATE_NEW — erstellt; wirft FileAlreadyExistsException, wenn sie existiert. Dieselbe Semantik wie Files.createFile.
  • TRUNCATE_EXISTING — löscht den Inhalt der Datei beim Öffnen (Standard für writeString, wenn nicht angehängt wird).
  • APPEND — schreibt am Ende, ohne zu kürzen.

Der Standard für Files.writeString (ohne Optionen) ist CREATE, WRITE, TRUNCATE_EXISTING — d. h. „erstellen oder überschreiben." Files.newBufferedWriter verhält sich standardmäßig genauso. Für Append-Semantik muss man dies explizit angeben.

Ein ausgearbeitetes Beispiel: Alle Ersteller nebeneinander

Das folgende Programm baut unter dem System-Temp-Verzeichnis von Grund auf einen kleinen Verzeichnisbaum auf — mithilfe beider APIs und mehrerer Open-Optionen. Jeder Schritt gibt aus, was sich geändert hat; der letzte Block zeigt, was passiert, wenn eine Operation gegen einen bereits vorhandenen Pfad wiederholt wird.

java— editable, runs on the server

Was man aus dem Lauf mitnehmen kann:

  • Das erste legacy.createNewFile() gab true zurück (erstellt); das zweite gab false zurück (bereits vorhanden). Das boolean gibt keine Auskunft darüber, was passiert ist — man muss sich die Konvention merken.
  • deep.mkdirs() war einmal erfolgreich und gab beim zweiten Aufruf false zurück. Dieses false sieht identisch aus wie „Berechtigung verweigert" oder „übergeordnetes Verzeichnis fehlt" — genau das fehlende-Fehlerinformations-Problem, das Files löst.
  • Files.createFile auf einem vorhandenen Pfad warf FileAlreadyExistsException. Der Exception-Typ ist spezifisch, sodass ein echter Handler zwischen „bereits vorhanden" und „Berechtigung verweigert" unterscheiden kann, ohne Strings zu parsen.
  • Files.createDirectories zweimal hintereinander aufgerufen hat beim zweiten Mal nichts Schädliches bewirkt. Das ist die Eigenschaft, die es zur richtigen Wahl in Startup-Code macht: kein Guard, einfach aufrufen.
  • Files.writeString(log, "line 1\n") hat die Datei erstellt, weil CREATE in den Standard-Optionen enthalten ist. Der zweite und dritte Aufruf verwendeten APPEND explizit, und die Datei sammelte drei Zeilen an. Der vierte Aufruf verwendete CREATE_NEW und weigerte sich zu überschreiben. Die Standards sind für den Fall „mit neuem Inhalt überschreiben" ausgelegt; Append muss explizit angegeben werden.
  • Files.createTempFile(root, "scratch-", ".tmp") erzeugte einen Namen wie scratch-1827392.tmp — Präfix und Suffix nach Wunsch, plus ein eindeutiges Stück, das die JVM wählt, damit zwei gleichzeitige Aufrufe nie kollidieren.
  • Das Cleanup geht root in umgekehrter Reihenfolge durch, damit Kindateien und -verzeichnisse vor ihren Elternverzeichnissen verschwinden. Files.delete verweigert das Löschen eines nicht leeren Verzeichnisses; diese Reihenfolge ist die Art, wie ein manuelles rm -rf aufgebaut wird.

Was kommt als Nächstes

Dateien können erstellt werden; das nächste Kapitel, Dateien in Java lesen, liest sie — zunächst mit den modernen Einzeilern (Files.readString, Files.readAllLines, Files.lines), dann mit dem klassischen FileReader / BufferedReader / Scanner-Dekorator-Stack, damit der ältere Code in den folgenden Kapiteln eine Grundlage hat.

Übungen

Übung
Sie möchten einen Einzeiler, der ein Verzeichnis samt aller fehlenden übergeordneten Verzeichnisse erstellt und **nichts** tut, wenn das Verzeichnis bereits existiert. Welcher Aufruf ist das?
Sie möchten einen Einzeiler, der ein Verzeichnis samt aller fehlenden übergeordneten Verzeichnisse erstellt und **nichts** tut, wenn das Verzeichnis bereits existiert. Welcher Aufruf ist das?
Was this page helpful?