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 missingDer 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 eineIOException. - Es gibt
falsezurü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 missingFileAlreadyExistsException 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 ancestorcreateDirectory 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-1827392Beide 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 existsCREATE— erstellt, wenn fehlend; andernfalls die vorhandene Datei öffnen.CREATE_NEW— erstellt; wirftFileAlreadyExistsException, wenn sie existiert. Dieselbe Semantik wieFiles.createFile.TRUNCATE_EXISTING— löscht den Inhalt der Datei beim Öffnen (Standard fürwriteString, 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.
Was man aus dem Lauf mitnehmen kann:
- Das erste
legacy.createNewFile()gabtruezurück (erstellt); das zweite gabfalsezurück (bereits vorhanden). Dasbooleangibt keine Auskunft darüber, was passiert ist — man muss sich die Konvention merken. deep.mkdirs()war einmal erfolgreich und gab beim zweiten Aufruffalsezurück. Diesesfalsesieht identisch aus wie „Berechtigung verweigert" oder „übergeordnetes Verzeichnis fehlt" — genau das fehlende-Fehlerinformations-Problem, dasFileslöst.Files.createFileauf einem vorhandenen Pfad warfFileAlreadyExistsException. Der Exception-Typ ist spezifisch, sodass ein echter Handler zwischen „bereits vorhanden" und „Berechtigung verweigert" unterscheiden kann, ohne Strings zu parsen.Files.createDirectorieszweimal 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, weilCREATEin den Standard-Optionen enthalten ist. Der zweite und dritte Aufruf verwendetenAPPENDexplizit, und die Datei sammelte drei Zeilen an. Der vierte Aufruf verwendeteCREATE_NEWund 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 wiescratch-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
rootin umgekehrter Reihenfolge durch, damit Kindateien und -verzeichnisse vor ihren Elternverzeichnissen verschwinden.Files.deleteverweigert das Löschen eines nicht leeren Verzeichnisses; diese Reihenfolge ist die Art, wie ein manuellesrm -rfaufgebaut 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.