- 1 Section
- 10 Lessons
- unbegrenzt
- Java Fortgeschritten10
- 1.1OOP in Java: Klassen, Kapselung, Konstruktoren
- 1.2Vererbung in Java
- 1.3Polymorphismus und Methodenüberschreibung
- 1.4Interfaces und abstrakte Klassen
- 1.5Collections: ArrayList, HashMap, LinkedList
- 1.6Generics und Typsicherheit
- 1.7Streams und Lambda-Ausdrücke
- 1.8Dateioperationen: IO und NIO
- 1.9Unit-Tests in Java: JUnit 5
- 1.10Praxisprojekt: Anwendung mit OOP und Tests
Dateioperationen: IO und NIO
Jedes ernsthafte Java-Programm muss früher oder später Dateien lesen oder schreiben – Konfigurationen einlesen, Logs schreiben, Datenexporte erzeugen, CSV-Dateien verarbeiten. Java bietet dafür zwei sich überlappende APIs: das ältere java.io aus Java 1.0 und das modernere java.nio.file seit Java 7.
Diese Lektion zeigt dir die wichtigsten Bausteine beider Welten, mit Fokus auf den modernen Files- und Path-Klassen aus NIO. Außerdem: wie man Exceptions korrekt behandelt, try-with-resources für sauberes Schließen nutzt und CSV-/JSON-Dateien einliest.
1) IO vs. NIO – das große Bild
Java hat zwei Generationen von Datei-APIs. Beide funktionieren – aber für neue Projekte ist NIO der Standard:
- Klassen:
File,FileReader,BufferedReader,FileWriter - Stream-orientiert (Bytes/Chars nacheinander)
- Viele kleine Klassen, schichtweise kombiniert
- Fehler oft nur als
boolean-Rückgabe - Standard in alten Codebasen
- Klassen:
Path,Paths,Files - Pfad-orientiert, viele bequeme statische Methoden
- Wirft beschreibende
IOExceptions - Direkter Stream-Support
- Empfohlen für neuen Code
2) Pfade mit Path und Paths
Ein Path repräsentiert einen Datei- oder Verzeichnispfad – plattformunabhängig:
import java.nio.file.Path; import java.nio.file.Paths; Path p1 = Path.of("daten.txt"); // relativ Path p2 = Path.of("/home/anna/log.txt"); // absolut Path p3 = Path.of("daten", "jan", "2026.csv"); // zusammengesetzt System.out.println(p3); // daten/jan/2026.csv (oder \ unter Windows) System.out.println(p3.getFileName()); // 2026.csv System.out.println(p3.getParent()); // daten/jan System.out.println(p3.toAbsolutePath()); // /pfad/zu/daten/jan/2026.csv
Der Konstruktor Path.of(...) (Java 11+) ersetzt das ältere Paths.get(...) – beide tun dasselbe. Path ist eine reine Datenstruktur und macht noch nichts mit dem Dateisystem.
3) Datei lesen – moderner Weg
Die Files-Klasse bietet bequeme Methoden zum Lesen ganzer Dateien:
import java.nio.file.Files; import java.nio.file.Path; Path pfad = Path.of("daten.txt"); // Komplette Datei als String String inhalt = Files.readString(pfad); // Als Liste von Zeilen List<String> zeilen = Files.readAllLines(pfad); // Als Stream (für große Dateien!) try (Stream<String> lines = Files.lines(pfad)) { lines.filter(z -> !z.isBlank()) .forEach(System.out::println); }
Drei Varianten mit unterschiedlichem Speicher-Verhalten:
readString– liest alles auf einmal in einen String. Praktisch, aber bei großen Dateien speicherintensiv.readAllLines– komplette Liste aller Zeilen. Auch alles im Speicher.lines– liefert einen Stream, der Zeilen lazy einliest. Ideal für riesige Dateien.
4) Datei schreiben
Die Gegenstücke zum Lesen:
Path pfad = Path.of("ausgabe.txt"); // String komplett schreiben Files.writeString(pfad, "Hallo Welt\n"); // Liste von Zeilen schreiben List<String> zeilen = List.of("Zeile 1", "Zeile 2", "Zeile 3"); Files.write(pfad, zeilen); // Anhängen statt überschreiben import java.nio.file.StandardOpenOption; Files.writeString(pfad, "\nNeu", StandardOpenOption.APPEND);
Standardmäßig überschreibt Files.writeString existierende Dateien und erzeugt neue, wenn nicht vorhanden. Mit den StandardOpenOption-Konstanten lässt sich das steuern: APPEND (anhängen), CREATE_NEW (Fehler wenn schon da), TRUNCATE_EXISTING u. a.
5) Dateioperationen im Überblick
6) try-with-resources
Ressourcen wie geöffnete Dateien müssen explizit geschlossen werden, sonst leaken Datei-Handles. Klassisch lief das so:
BufferedReader br = null; try { br = new BufferedReader(new FileReader("daten.txt")); // ... lesen ... } finally { if (br != null) br.close(); }
Seit Java 7 gibt es try-with-resources: Ressourcen werden in den runden Klammern deklariert und am Ende des Try-Blocks automatisch geschlossen – selbst bei einer Exception:
try (BufferedReader br = Files.newBufferedReader(Path.of("daten.txt"))) { String zeile; while ((zeile = br.readLine()) != null) { System.out.println(zeile); } } catch (IOException e) { System.err.println("Fehler: " + e.getMessage()); }
Jede Klasse, die AutoCloseable implementiert, kann in try-with-resources stehen – Streams, Reader, Writer, Datenbank-Verbindungen, HTTP-Clients und vieles mehr.
7) Checked Exceptions verstehen
Praktisch alle Datei-Operationen werfen IOException – eine Checked Exception. Sie zwingt dich, sie zu behandeln oder weiterzureichen:
// Methode reicht weiter public void leseDatei() throws IOException { String s = Files.readString(Path.of("x.txt")); } // Aufrufer fängt try { leseDatei(); } catch (IOException e) { e.printStackTrace(); }
Wichtige Exception-Typen aus der Datei-Welt:
IOException– Oberklasse für alle Datei- und Netzwerk-ProblemeFileNotFoundException– Datei existiert nichtNoSuchFileException– ähnlich, aus NIOAccessDeniedException– fehlende BerechtigungenFileAlreadyExistsException– Datei existiert schon (beiCREATE_NEW)
catch (Exception e) { } ist eine der schlimmsten Sünden – Fehler verschwinden spurlos, Bugs lassen sich nicht mehr finden. Mindestens loggen, besser dem Aufrufer melden.
8) Datei zeilenweise verarbeiten
Häufiger Anwendungsfall: eine große Logdatei nach Mustern durchsuchen. Mit Files.lines() und Streams elegant:
try (Stream<String> lines = Files.lines(Path.of("server.log"))) { long errorCount = lines .filter(z -> z.contains("ERROR")) .count(); System.out.println("Fehler: " + errorCount); } catch (IOException e) { e.printStackTrace(); }
Der Stream liest die Datei zeilenweise „on the fly" – auch eine 10-GB-Datei passt locker durch. Bei readAllLines wäre der Speicher voll, lange bevor wir fertig sind.
9) CSV-Dateien einlesen
CSV ist das einfachste Tabellen-Format und überall gebräuchlich. Naiver Ansatz mit String-Split:
try (Stream<String> lines = Files.lines(Path.of("kunden.csv"))) { lines.skip(1) // Header überspringen .map(z -> z.split(",")) .forEach(felder -> System.out.println(felder[0] + " -> " + felder[1]) ); }
Funktioniert für saubere Daten, scheitert aber bei komplexem CSV: Kommata in Feldern, Anführungszeichen, Zeilenumbrüche, andere Trennzeichen. Für ernsthaftes CSV nimmst du eine Bibliothek wie OpenCSV oder Apache Commons CSV – die kümmern sich um alle Spezialfälle.
10) JSON lesen und schreiben
Für JSON ist die Standardlösung in Java der Jackson-Library:
// Maven-Dependency: com.fasterxml.jackson.core:jackson-databind import com.fasterxml.jackson.databind.ObjectMapper; ObjectMapper mapper = new ObjectMapper(); // Java-Objekt → JSON-Datei Kunde k = new Kunde("Anna", 28); mapper.writeValue(Path.of("k.json").toFile(), k); // JSON-Datei → Java-Objekt Kunde wieder = mapper.readValue( Path.of("k.json").toFile(), Kunde.class);
Jackson nutzt die Getter/Setter-Konvention aus L1, um Felder automatisch zu mappen. Mehr Konfiguration braucht es nur bei besonderen Anforderungen (Datumsformat, Feldumbenennung).
11) Verzeichnisse durchsuchen
Mit Files.walk(...) bekommst du einen Stream aller Dateien in einem Verzeichnisbaum:
try (Stream<Path> paths = Files.walk(Path.of("src"))) { paths .filter(Files::isRegularFile) .filter(p -> p.toString().endsWith(".java")) .forEach(System.out::println); }
Variante Files.list(...) liefert nur die direkten Einträge eines Verzeichnisses, ohne Unterordner rekursiv zu durchsuchen.
12) IO-Architektur: Streams in der alten Welt
In der älteren java.io-API arbeitet man mit verschachtelten Streams, die Funktionalität dekorieren – ein Reader liest, ein BufferedReader puffert, ein PrintWriter formatiert:
BufferedReader – zeilenweise Pufferung, hat .readLine()
InputStreamReader – wandelt Bytes in Zeichen (mit Encoding)
FileInputStream – liest Bytes aus der Datei
13) Encoding beachten
Wichtigste Falle bei Textdateien: das Zeichen-Encoding. Eine Datei kann UTF-8, ISO-8859-1, UTF-16 oder andere Codierungen verwenden. Wenn du das falsche annimmst, kommen kaputte Zeichen heraus:
// Standard ist UTF-8 (seit Java 18 für alle Plattformen) String s = Files.readString(pfad); // Explizit Encoding setzen String s2 = Files.readString(pfad, StandardCharsets.ISO_8859_1);
Faustregel: alles neu in UTF-8 speichern. Alte Dateien mit Umlauten in Windows-1252 brauchen explizites Charset.
14) Performance-Hinweise
- Streams für große Dateien:
Files.lines()stattreadAllLines() - Puffern:
BufferedReader/BufferedWritermachen häufige kleine Operationen viel schneller - Binärdaten über
InputStream/OutputStream, nicht über Reader/Writer (die sind für Text) - Festplatte vs. SSD: SSDs sind viel toleranter für viele kleine I/Os; Festplatten profitieren stark vom Puffern
- Try-with-resources immer nutzen: nicht geschlossene Streams führen zu Resource-Leaks und im Extremfall zum Absturz der JVM mit „Too many open files"
15) Häufige Fehler
- Datei nicht geschlossen: ohne try-with-resources bleibt das Datei-Handle offen, irgendwann verweigert das System weitere Dateien
- readAllLines bei riesigen Dateien: OutOfMemoryError. Lieber lines()
- Pfade als String hartcodiert:
"C:\Users\anna"klappt nicht auf Linux.Path.of(...)ist plattformunabhängig - Encoding ignoriert: Datei mit Umlauten gibt kaputte Zeichen aus, wenn das falsche Charset angenommen wird
- Exception unterdrückt: leeres catch macht Fehler unsichtbar
- String += in Schleife beim Bauen einer Datei: extrem langsam. Stattdessen
StringBuilderoder direkt zeilenweise schreiben - Datei vor dem Schreiben nicht angelegt:
Files.writeStringlegt automatisch an. Aber bei manchen alten IO-Klassen muss das Verzeichnis existieren
IOException und dem Umgang mit Pfaden. Das volle NIO-Spektrum (Channels, ByteBuffers) ist kaum prüfungsrelevant.
Zusammenfassung
Java bietet zwei Datei-APIs: das alte java.io (Streams, viele Klassen, Decorator-Pattern) und das moderne java.nio.file mit den Klassen Path, Paths und vor allem Files. Für neuen Code: NIO bevorzugen. Files.readString, Files.readAllLines und Files.lines (Stream-basiert für große Dateien) sind die wichtigsten Lese-Methoden, Files.writeString und Files.write zum Schreiben. Datei-Operationen werfen IOException (Checked Exception) – mit try-with-resources werden Streams und Reader automatisch geschlossen. Zeichen-Encoding (Standard UTF-8) immer im Blick behalten; Path.of(...) macht Pfade plattformunabhängig. Für CSV/JSON empfehlen sich Bibliotheken wie OpenCSV oder Jackson.
