Dateien
Kurz gesagt: Eine Datei behält die Daten auf der Festplatte, auch nachdem das Programm endet; in Java lesen und schreiben Path und Files Text, und try-with-resources schließt die Datei für dich.
Problem
Das Programm vergisst
Du schreibst ein Programm für deine Ausgaben. Du gibst sie abends ein und schließt es, und am nächsten Tag weiß es nichts mehr von gestern. Hier ist das kürzeste Beispiel:
int visits = 0;visits++;System.out.println("Gestartet: " + visits + " Mal.");Gestartet: 1 Mal.Egal wie oft du es startest, es sagt „1 Mal“. Variablen leben im Speicher, solange das Programm läuft. Wenn es endet, wird der Speicher freigegeben, und alles verschwindet.
Damit es sich erinnert, muss das Programm die Daten dort ablegen, wo sie auch nach seinem Ende bleiben: in einer Datei auf der Festplatte.
Die Idee
Pfad und Datei
Hier ist ein ganzes Programm, das eine Notiz in eine Datei schreibt und sie wieder liest:
import java.io.IOException;import java.nio.file.Files;import java.nio.file.Path;
public class Main { public static void main(String[] args) { Path notes = Path.of("notes.txt"); try { Files.writeString(notes, "Milch kaufen\nStromrechnung bis zum 15. bezahlen\n"); String text = Files.readString(notes); System.out.print(text); } catch (IOException e) { System.out.println("Problem mit der Datei: " + e.getMessage()); } }}Milch kaufenStromrechnung bis zum 15. bezahlenPathist der Pfad zur Datei: „wo sie ist“.Path.of("notes.txt")ist eine Datei mit diesem Namen in dem Ordner, aus dem das Programm gestartet wurde.Fileshat statische Methoden für alles rund um Dateien.writeStringschreibt Text (und legt die Datei an, wenn es sie nicht gibt),readStringliest sie ganz.IOExceptionist eine geprüfte Exception (checked), aus dem Modul „Fehler und Exceptions“. Die Festplatte kann voll sein, die Datei gesperrt oder nicht da, und der Compiler zwingt dich zu sagen, was dann passieren soll.
Starte das Programm und öffne dann den Ordner: notes.txt ist da, und du kannst sie mit jedem Texteditor öffnen. Der nächste Start findet sie.
In den Beispielen ab hier werden die imports nicht gezeigt. Alles ist aus java.nio.file und java.io, und List ist aus java.util.
Die Idee
Zeilen anhängen und lesen
Ausgaben kommen eine nach der anderen dazu. StandardOpenOption.APPEND hängt am Ende an, statt die Datei neu zu schreiben. readAllLines liest die Datei als Zeilen:
Path expenses = Path.of("expenses.txt");try { Files.writeString(expenses, "Mittagessen;12.50\n"); Files.writeString(expenses, "Taxi;8.20\n", StandardOpenOption.APPEND); Files.writeString(expenses, "Buch;18.90\n", StandardOpenOption.APPEND); List<String> lines = Files.readAllLines(expenses); double total = 0; for (String line : lines) { String[] parts = line.split(";"); total += Double.parseDouble(parts[1]); System.out.println(parts[0] + ": " + parts[1] + " €"); } System.out.printf("%d Ausgaben, gesamt %.2f €%n", lines.size(), total);} catch (IOException e) { System.out.println("Problem mit der Datei: " + e.getMessage());}Mittagessen: 12.50 €Taxi: 8.20 €Buch: 18.90 €3 Ausgaben, gesamt 39.60 €readAllLines gibt eine List<String> zurück: eine Liste von Strings, einer für jede Zeile. Listen kommen im nächsten Modul, „Collections“. Für jetzt brauchst du nur zwei Dinge: sie mit for-each durchlaufen, wie ein Array, und size() für die Anzahl der Zeilen.
Genauer, für Neugierige
APPENDallein legt die Datei nicht an. Wenn es sie nicht gibt, bekommst duNoSuchFileException. Für „anlegen oder anhängen“ gibst du beides an:StandardOpenOption.CREATE, StandardOpenOption.APPEND.- Seit Java 18 ist Text in Dateien standardmäßig in UTF-8, und Umlaute wie „ä“ und das „€“ werden richtig gelesen und geschrieben. In älteren Versionen hing es von den Einstellungen des Computers ab. Wenn du eine Datei von einem anderen System liest, kannst du die Kodierung ausdrücklich angeben:
Files.readString(path, StandardCharsets.UTF_8). readAllLinesundreadStringlesen die ganze Datei in den Speicher. Bei einer Datei von mehreren Gigabyte liest du besser Zeile für Zeile. Siehe die Karte überBufferedReader.
Probier es aus
Zwei Schichten
Was gibt das Programm aus?
Path shift = Path.of("shift.txt");try { Files.writeString(shift, "Erste Schicht: Jonas\n"); Files.writeString(shift, "Zweite Schicht: Maria\n"); System.out.print(Files.readString(shift));} catch (IOException e) { System.out.println(e.getMessage());}writeString ohne APPEND löscht den alten Inhalt und schreibt den neuen. Die erste Zeile ist schon vor dem Lesen weg. Das ist der häufigste Grund für „meine Datei wurde gelöscht“.
Die Idee
Wenn die Datei nicht da ist
Die Einstellungen des Programms stehen in config.txt, und jemand hat sie gelöscht. Wenn niemand die Exception fängt:
import java.io.IOException;import java.nio.file.Files;import java.nio.file.Path;
public class Main { static String readConfig() throws IOException { return Files.readString(Path.of("config.txt")); }
public static void main(String[] args) throws IOException { System.out.println(readConfig()); }}Exception in thread "main" java.nio.file.NoSuchFileException: config.txt at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92) ... at java.base/java.nio.file.Files.readString(Files.java:3318) at Main.readConfig(Main.java:7) at Main.main(Main.java:11)Die Zeilen aus java.base sind das Innere von Java (hier mit „...“ gekürzt). Dein Code steht ganz unten: Zeile 7 in readConfig, aufgerufen aus Zeile 11 in main. Auch main hat throws IOException angegeben, deshalb erreicht die Exception die JVM, und das Programm hält an.
NoSuchFileException ist eine Unterklasse von IOException. Fang sie getrennt, vor dem allgemeinen Fall:
static String readConfig() throws IOException { return Files.readString(Path.of("config.txt"));}
try { System.out.println(readConfig());} catch (NoSuchFileException e) { System.out.println("Keine Datei " + e.getMessage() + ". Ich nehme die Standardeinstellungen.");} catch (IOException e) { System.out.println("Kann die Einstellungen nicht lesen: " + e.getMessage());}System.out.println(Files.exists(Path.of("config.txt")));Keine Datei config.txt. Ich nehme die Standardeinstellungen.falseFiles.exists(path) prüft, ob die Datei existiert. Die Prüfung ersetzt das catch nicht: Die Datei kann zwischen der Prüfung und dem Lesen verschwinden, und es gibt noch andere Gründe, warum das Lesen scheitern kann.
Die Idee
Zeile für Zeile und try-with-resources
Für eine große Datei oder zum Schreiben in Teilen gibt es BufferedWriter und BufferedReader. Sie halten die Datei offen, solange du mit ihr arbeitest, und müssen deshalb am Ende geschlossen werden:
Path report = Path.of("report.txt");try (BufferedWriter out = Files.newBufferedWriter(report)) { for (int day = 1; day <= 3; day++) { out.write("Tag " + day + ": " + day * 120 + " €"); out.newLine(); }} catch (IOException e) { System.out.println("Kann nicht schreiben: " + e.getMessage());}
try (BufferedReader in = Files.newBufferedReader(report)) { String line; while ((line = in.readLine()) != null) { System.out.println(line); }} catch (IOException e) { System.out.println("Kann nicht lesen: " + e.getMessage());}Tag 1: 120 €Tag 2: 240 €Tag 3: 360 €writeschreibt Text,newLine()geht in eine neue Zeile.readLine()gibt die nächste Zeile zurück odernull, wenn die Datei zu Ende ist.try (...)mit einer Ressource, die in den Klammern erzeugt wird, heißt try-with-resources. Wenn der Block endet, egal auf welche Weise, schließt sich die Ressource von selbst.
Das Schließen ist keine Formalität: Solange der BufferedWriter offen ist, kann der Text noch in seinem Puffer stehen und nicht auf der Festplatte. Damit der Code kurz bleibt, hat main hier throws IOException:
Path report = Path.of("report.txt");try (BufferedWriter out = Files.newBufferedWriter(report)) { for (int day = 1; day <= 3; day++) { out.write("Tag " + day + ": " + day * 120 + " €"); out.newLine(); } System.out.println("Im Block: " + Files.size(report) + " Bytes");}System.out.println("Nach dem Block: " + Files.size(report) + " Bytes");for (String line : Files.readAllLines(report)) { System.out.println(line);}Schritt für Schritt, in Worten
- Path ist nur die Adresse der Datei. Links ist der Speicher des Programms, rechts die Festplatte.
- newBufferedWriter legt die leere Datei report.txt an und ein Objekt mit einem Puffer im Speicher.
- write und newLine schreiben in den Puffer. Auf der Festplatte kommt noch nichts an.
- Die Datei auf der Festplatte hat noch 0 Bytes.
- Der Block endet, und try ruft close() auf. Der Puffer geht auf die Festplatte: Die Datei hat jetzt 45 Bytes.
- 45 Bytes für 39 Zeichen mit den Zeilenumbrüchen: In UTF-8 ist jeder lateinische Buchstabe 1 Byte, und € sind 3.
- readAllLines liest die drei Zeilen von der Festplatte zurück.
Das haben wir in der Lektion über finally versprochen. Hier ist dasselbe Schreiben mit finally:
BufferedWriter out = null;try { out = Files.newBufferedWriter(report); out.write("Tag 1: 120 €");} catch (IOException e) { System.out.println("Kann nicht schreiben: " + e.getMessage());} finally { if (out != null) { try { out.close(); } catch (IOException e) { System.out.println("Kann nicht schließen: " + e.getMessage()); } }}Fünfzehn Zeilen für ein einziges Schreiben, und auch close() kann eine IOException werfen. try (...) erledigt genau das für dich.
Genauer, für Neugierige
- In den Klammern können mehrere Ressourcen stehen, getrennt durch
;. Sie werden in umgekehrter Reihenfolge geschlossen: Die zuletzt geöffnete wird zuerst geschlossen. - Puffer.
BufferedWritersammelt den Text im Speicher und schickt ihn in großen Stücken auf die Festplatte. Jeder einzelne Schreibvorgang auf die Festplatte ist langsam, und so gibt es nur wenige davon. Genauso liestBufferedReaderviel auf einmal. - Bytes und Zeichen. Darunter ist eine Datei eine Folge von Bytes.
InputStreamundOutputStream(zum BeispielFiles.newInputStream) lesen und schreiben Bytes: für Fotos, Archive, alles, was kein Text ist.ReaderundWriterverwandeln Bytes nach der Kodierung in Zeichen.BufferedReaderist einReadermit Puffer. - Der ältere Weg. In fremdem Code siehst du
new BufferedReader(new FileReader("report.txt"))undnew Scanner(new File("report.txt")). Sie funktionieren, aberFilesist neuer und bequemer.Scannerliest auch aus einer Datei, mit denhasNextLineundnextLine, die du schon kennst:
try (Scanner in = new Scanner(Path.of("report.txt"))) { while (in.hasNextLine()) { System.out.println(in.nextLine()); }}Probier es aus
Wann wird geschlossen?
Jede Klasse, die das Interface AutoCloseable implementiert (die Methode close()), kann in einem try (...) stehen. Hier ist der Kassendrucker aus der Lektion über finally, als Ressource:
Was gibt das Programm aus?
class Printer implements AutoCloseable { Printer() { System.out.println("Der Drucker ist belegt."); }
public void close() { System.out.println("Der Drucker ist frei."); }}
try (Printer printer = new Printer()) { System.out.println("Drucke den Kassenbon..."); int qty = Integer.parseInt("drei"); System.out.println("Menge: " + qty);} catch (NumberFormatException e) { System.out.println("Falsche Menge.");}parseInt("drei") wirft, und die Zeile „Menge“ wird übersprungen. Beim Verlassen von try wird die Ressource sofort geschlossen, noch vor catch. Deshalb kommt „frei“ vor „Falsche Menge.“ Wenn catch läuft, ist der Drucker schon freigegeben.Probier es aus
Finde den Fehler
0 aus. In welcher Zeile ist der Fehler?Wähle die Zeile mit dem Fehler.
BufferedWriter wird nie geschlossen. Der Text steht noch im Puffer im Speicher und hat die Datei nicht erreicht, deshalb ist die Datei leer. Das Programm meldet keinen Fehler. Es verliert still Daten. Öffne ihn in try (BufferedWriter out = ...) { ... } und lies die Datei nach dem Block: Dann ist er geschlossen, und die Zeile ist da.Die Idee
Ordner
Die Sicherungskopien der Kasse kommen in einen Ordner, eine pro Tag:
Path backup = Path.of("backup");try { Files.createDirectories(backup.resolve("old")); Files.writeString(backup.resolve("2026-10-01.csv"), "Brötchen,0.60\n"); Files.writeString(backup.resolve("2026-10-02.csv"), "Brötchen,0.60\nTee,1.80\n"); String[] names = backup.toFile().list(); Arrays.sort(names); for (String name : names) { Path p = backup.resolve(name); if (Files.isDirectory(p)) { System.out.println(name + "/"); } else { System.out.println(name + " (" + Files.size(p) + " Bytes)"); } } Files.delete(backup.resolve("2026-10-01.csv")); System.out.println(Files.exists(backup.resolve("2026-10-01.csv")));} catch (IOException e) { System.out.println(e.getMessage());}2026-10-01.csv (15 Bytes)2026-10-02.csv (24 Bytes)old/false| Methode | Was sie tut |
|---|---|
Files.createDirectories(p) |
legt den Ordner und alle Ordner darüber an, wenn es sie nicht gibt |
dir.resolve("name") |
der Pfad zu name in dir |
Files.isDirectory(p), Files.exists(p) |
ist es ein Ordner, ist es da |
Files.size(p) |
die Größe in Bytes |
Files.delete(p) |
löscht eine Datei oder einen leeren Ordner |
dir.toFile().list() |
ein Array mit den Namen von allem im Ordner |
Die Größen sind 15 und 24 Bytes, obwohl es weniger Zeichen sind: In UTF-8 ist das „ö“ in „Brötchen“ 2 Bytes.
list() gibt die Namen in keiner bestimmten Reihenfolge, deshalb sortieren wir sie mit java.util.Arrays.sort.Genauer, für Neugierige
Files.deletewirftDirectoryNotEmptyException, wenn der Ordner nicht leer ist, undNoSuchFileException, wenn es ihn nicht gibt.Files.deleteIfExistsbeschwert sich nicht, wenn es ihn nicht gibt.Files.list(dir)ist der neue Weg zum Inhalt eines Ordners, aber es gibt einenStreamzurück, der im Modul „Modernes Java“ kommt.Files.copyundFiles.movekopieren und verschieben.- In älterem Code siehst du die Klasse
java.io.File:new File("notes.txt")mit den Methodenexists(),length(),delete(),renameTo(...),createNewFile()undgetAbsolutePath(). Sie gab es vorPathundFiles, undpath.toFile()undfile.toPath()verwandeln das eine in das andere. Dort ist das Anhängen am Endenew FileWriter("output.txt", true). - Der Pfad
"notes.txt"ist relativ: zu dem Ordner, aus dem das Programm gestartet wurde.Path.of("/home/maria/notes.txt")ist absolut.path.toAbsolutePath()zeigt genau, wo die Datei ist.
Übung
Programme, die sich erinnern
Löse die Aufgaben in deinem Editor. In jeder Aufgabe legt das Programm die Eingabedatei zuerst selbst an, mit Files.writeString, damit du es sofort ausprobieren kannst.
1. Eine Konfigurationsdatei. Lege server.conf mit diesem Inhalt an, lies sie dann und gib jede Einstellung als Schlüssel: Wert aus. Überspringe die leeren Zeilen und die Zeilen, die mit # beginnen. Tipp: split("=", 2) teilt nur am ersten =.
# Servereinstellungenmax_connections=500log_file_path=/var/log/server.log
db_timeout=30max_connections: 500log_file_path: /var/log/server.logdb_timeout: 302. Ein Filter für eine Logdatei. Lege app.log mit vier Zeilen an (zwei INFO und zwei ERROR). Lies ein Level von der Tastatur, zum Beispiel ERROR, und schreib die Zeilen mit diesem Level in errors.log mit einem BufferedReader und einem BufferedWriter in einem try (...). Gib errors.log aus und wie viele Zeilen du geschrieben hast.
ERRORERROR: Failed to load configurationERROR: Null pointer exception2 Zeilen in errors.log geschrieben3. Der Inhalt eines Ordners. Lege einen Ordner project mit den Dateien document.txt, image.jpg, script.js und dem Ordner backup an. Schreib ihre Namen, alphabetisch sortiert, in directory_contents.txt, mit einem / nach den Namen von Ordnern. Gib die Datei aus.
backup/document.txtimage.jpgscript.js4. Eine Indexdatei. Lege text.txt mit den drei Zeilen unten an. Lies ein Wort von der Tastatur und schreib in index.txt jede Stelle, an der es vorkommt, in der Form Zeile:Position: die Nummer der Zeile (ab 1) und den Index in der Zeile (ab 0). Wenn das Wort zweimal in einer Zeile steht, sind das zwei Einträge.
hello worldworld hellohello again, hellohello1:02:63:03:13Weitere Aufgaben
- Zähler für die Starts. Repariere das Programm aus der ersten Karte: Zähl die Starts in
visits.txt. Wenn es die Datei nicht gibt, fang bei 0 an. - Suchen und Ersetzen in einer Datei. Die Aufgabe aus der Lektion „Reguläre Ausdrücke: Gruppen und Ersetzen“: Lies eine Datei, ersetze „alt“ durch „neu“ und schreib das Ergebnis in eine neue Datei.
- Datumsangaben aus einer Datei. Die Datumsaufgabe aus der Lektion „Reguläre Ausdrücke“: Lies die Datumsangaben aus einer Datei, eine pro Zeile.
- Eine Kopie mit Zeilennummern. Lies eine Datei und schreib eine Kopie, in der jede Zeile mit ihrer Nummer beginnt:
1: ...,2: ....
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Interfaces · Exceptions · try, catch und finally · Strings
- Geht weiter in: Daten in Dateien · Hashcodes und Hashfunktionen · Netzwerkprogrammierung · Datenbanken
- Aufgaben zum Thema: Text und Dateien