CodeGym /Kurse /JAVA 25 SELF /BufferedReader, BufferedWriter: Pufferung, Vorteile

BufferedReader, BufferedWriter: Pufferung, Vorteile

JAVA 25 SELF
Level 36 , Lektion 0
Verfügbar

1. Einführung

Erinnern wir uns daran, wie FileReader und FileWriter arbeiten. Jedes Mal, wenn Sie deren Methode read() oder write() aufrufen, erfolgt ein Zugriff auf das Dateisystem – der Rechner greift tatsächlich auf die Festplatte zu, um genau ein Zeichen zu lesen oder zu schreiben. Ist die Datei groß und Sie lesen oder schreiben Zeichen für Zeichen, wird das zu einem Marathon über Tausende oder sogar Millionen von Plattenzugriffen. Und die Festplatte ist bekanntlich nicht der schnellste Freund von Programmiererinnen und Programmierern.

Man kann es sich so vorstellen: Sie müssen Wasser aus einem Eimer in eine Flasche umfüllen und benutzen dafür einen Teelöffel. Formal funktioniert das, aber es dauert furchtbar lange. Viel vernünftiger ist es, einen Schöpflöffel oder einen Becher zu nehmen. Genauso bei Dateien: Zeichenweise zu lesen oder zu schreiben ist, als würde man Wasser mit dem Löffel tragen.

So sieht das aus:

// Zeichenweises Lesen der Datei (mit "Teelöffel"!)
try (FileReader reader = new FileReader("big.txt")) {
    int c;
    while ((c = reader.read()) != -1) {
        // Symbol verarbeiten (z. B. einfach zählen)
    }
}

Wenn die Datei groß ist, werden Sie merken, dass das Programm sehr langsam läuft.

Pufferung: Was ist das und wozu braucht man sie

Ein Puffer ist ein spezieller Speicherbereich (meist ein Array), in den Daten nicht Zeichen für Zeichen, sondern in größeren Blöcken (z. B. in 8 KB oder mehr) geladen oder geschrieben werden. Vereinfacht gesagt: Ein Puffer ist ein Stück Speicher – eine Art Zwischen-Eimer –, in den Daten nicht einzeln, sondern in größeren Portionen gelesen oder geschrieben werden.

So funktioniert es: Beim Lesen greift das Programm einmal auf die Festplatte zu, lädt einen ganzen Datenblock in den Puffer und verteilt ihn anschließend bequem Zeichen für Zeichen aus dem Speicher. Sobald der Block zu Ende ist, wird der nächste nachgeladen. Beim Schreiben gilt dieselbe Logik: Die Daten werden zunächst im Puffer gesammelt und erst dann in einem größeren Stück auf die Festplatte geschrieben (oder sofort, wenn Sie flush() aufrufen).

Warum ist das schneller? Weil die Festplatte an sich langsam ist, insbesondere bei vielen Kleinstzugriffen. Wenn man jedoch seltener zugreift, dafür aber mehr auf einmal, läuft alles spürbar schneller. Kurz gesagt: Pufferung spart Plattenzugriffe und beschleunigt das Programm.

2. BufferedReader und BufferedWriter: Syntax und Beispiele

In Java werden zwei Klassen zum Puffern des Lesens und Schreibens von Textdateien verwendet:

  • BufferedReader – zum Lesen von Textdateien.
  • BufferedWriter – zum Schreiben von Textdateien.

Sie setzen auf normalen Reader/Writer auf (z. B. FileReader/FileWriter) und fügen Pufferung hinzu.

Datei zeilenweise mit BufferedReader lesen

Das häufigste Szenario ist, eine Datei zeilenweise zu lesen. Die Methode readLine() gibt die Zeichen bis zum Zeilenumbruch zurück ("\n" oder "\r\n").

import java.io.*;

public class BufferedReaderExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line); // Zeile auf dem Bildschirm ausgeben
            }
        } catch (IOException e) {
            System.out.println("Fehler beim Lesen der Datei: " + e.getMessage());
        }
    }
}

Was passiert hier?

Wir erstellen einen FileReader, der Zeichen für Zeichen aus einer Datei lesen kann, und hüllen ihn in einen BufferedReader. Der gepufferte Reader holt die Daten nicht einzeln, sondern in größeren Stücken, legt sie im Speicher ab und gibt sie uns zeilenweise über die Methode readLine() aus. Am Ende schreiben Sie einfach eine while-Schleife, erhalten die Zeilen nacheinander und brauchen sich nicht um die Dateigröße zu kümmern: Das Lesen bleibt schnell und ressourcenschonend.

Datei mit BufferedWriter schreiben

Auch das Schreiben von Zeilen in eine Datei geht effizient:

import java.io.*;

public class BufferedWriterExample {
    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
            writer.write("Hello, world!");
            writer.newLine(); // Zeilenumbruch (abhängig vom Betriebssystem)
            writer.write("Das ist die zweite Zeile.");
        } catch (IOException e) {
            System.out.println("Fehler beim Schreiben der Datei: " + e.getMessage());
        }
    }
}

Was passiert hier?

Zuerst erzeugen wir einen FileWriter und hüllen ihn dann in einen BufferedWriter. Wenn Sie write() oder newLine() aufrufen, gehen die Daten nicht sofort auf die Festplatte. Sie werden im Puffer – einer speziellen Zwischenspeicher-Schicht – gesammelt. Erst wenn der Puffer voll ist, Sie den Stream schließen (oder explizit flush() aufrufen), wird der gesamte Text in einem Rutsch in die Datei geschrieben. Dieser Ansatz beschleunigt das Schreiben deutlich und spart Plattenzugriffe.

Wie sieht das in einer kleinen Anwendung aus?

Nehmen wir an, wir schreiben eine einfache „Tagebuch“-App, die Einträge in eine Datei speichert und sie auf dem Bildschirm anzeigt.

import java.io.*;
import java.util.Scanner;

public class DiaryApp {
    public static void main(String[] args) {
        String fileName = "diary.txt";
        Scanner scanner = new Scanner(System.in);

        // Neuen Eintrag speichern
        System.out.print("Geben Sie einen neuen Tagebucheintrag ein: ");
        String entry = scanner.nextLine();

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName, true))) {
            writer.write(entry);
            writer.newLine();
            System.out.println("Eintrag gespeichert!");
        } catch (IOException e) {
            System.out.println("Fehler beim Schreiben: " + e.getMessage());
        }

        // Alle Einträge lesen
        System.out.println("\nIhr Tagebuch:");
        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Fehler beim Lesen: " + e.getMessage());
        }
    }
}

Wir nehmen den Dateinamen "diary.txt" und bitten die Nutzerin/den Nutzer, einen neuen Eintrag einzugeben. Zum Speichern verwenden wir FileWriter im append-Modus (wir übergeben true), sodass alte Notizen nicht überschrieben werden – jede neue Zeile wird sauber ans Dateiende angehängt. Die Hülle BufferedWriter macht das Schreiben schnell und sparsam: Die Daten sammeln sich zunächst im Speicher und gehen dann in einem Stück auf die Festplatte.

Anschließend öffnen wir dieselbe Datei zum Lesen über BufferedReader. Er holt den Inhalt in großen Blöcken und gibt Ihnen die Zeilen einzeln aus. In der Schleife gibt das Programm sie einfach auf dem Bildschirm aus, und am Ende sehen Sie Ihr gesamtes „Tagebuch“ von Anfang bis Ende.

3. Vorteile von BufferedReader und BufferedWriter

Deutliche Beschleunigung

Wenn Sie mit gepufferten Streams lesen oder schreiben, macht das Programm um ein Vielfaches weniger Plattenzugriffe – teils Dutzende, teils Hunderte Male weniger. Das zeigt sich besonders bei großen Dateien.

Praktische Methoden

  • BufferedReader.readLine() – ermöglicht zeilenweises Lesen, was für die Verarbeitung von Textdateien (z. B. Logs, CSV, Konfigurationen) sehr praktisch ist.
  • BufferedWriter.newLine() – fügt einen Zeilenumbruch hinzu und setzt das passende Zeichen je nach Betriebssystem.

Einfache Verwendung

  • Die Klassen lassen sich leicht mit anderen Streams kombinieren (z. B. kann man InputStreamReader innerhalb von BufferedReader verwenden, um Dateien mit verschiedenen Zeichencodierungen zu lesen).
  • Bei Verwendung von try-with-resources werden alle Ressourcen automatisch geschlossen.

Flexibilität

  • Die Puffergröße lässt sich explizit festlegen, falls der Standardwert (meist 8 KB) zu klein oder zu groß ist:
BufferedReader reader = new BufferedReader(new FileReader("big.txt"), 16384); // Puffer von 16 KB

4. Wann sollte man BufferedReader und BufferedWriter verwenden

Verwenden Sie sie, wenn:

  • Sie mit Textdateien arbeiten (Logs, CSV, große Textdaten).
  • Sie eine Datei zeilenweise lesen oder schreiben müssen.
  • Sie bei großen Dateien die Leistung steigern wollen, weil Geschwindigkeit wichtig ist.
  • Sie einen Datenstrom aus dem Netzwerk oder einer anderen Quelle verarbeiten, die Reader/Writer unterstützt.

Verwenden Sie sie nicht, wenn:

  • Sie mit Binärdateien arbeiten (z. B. Bilder, Archive, Videos) – dafür gibt es InputStream/OutputStream.
  • Die Datei sehr klein ist und einmal komplett gelesen/geschrieben wird – hier ist der Gewinn durch Pufferung minimal (schaden wird sie aber auch nicht).

5. Nützliche Details

Kombination mit verschiedenen Zeichencodierungen

Wenn Sie Dateien in einer bestimmten Zeichencodierung lesen/schreiben müssen (z. B. "UTF-8", "Windows-1251"), verwenden Sie InputStreamReader/OutputStreamWriter in Kombination mit gepufferten Streams:

BufferedReader reader = new BufferedReader(
    new InputStreamReader(new FileInputStream("input.txt"), "UTF-8")
);

BufferedWriter writer = new BufferedWriter(
    new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8")
);

Explizites Leeren des Puffers

Manchmal ist es wichtig, dass die Daten sicher auf die Festplatte geschrieben werden (z. B. beim Schreiben eines Logs oder einer Quittung). Rufen Sie dafür writer.flush() auf. Üblicherweise ist das nicht nötig, da das Schließen des Streams den Puffer automatisch leert.

Puffergröße

Standardmäßig beträgt die Puffergröße etwa 8 KB. Sie können eine andere Größe festlegen, wenn Sie genau wissen, dass das die Leistung verbessert (z. B. bei der Verarbeitung sehr großer Dateien).

Vergleich: FileReader/FileWriter vs. BufferedReader/BufferedWriter

Klasse Geschwindigkeit bei großen Dateien Komfort beim zeilenweisen Lesen Komfort beim zeilenweisen Schreiben Flexibilität bei Zeichencodierungen
FileReader/FileWriter Langsam Nein (nur zeichenweise) Nein (nur zeichenweise) Nur standardmäßig
BufferedReader/Writer Schnell Ja (readLine()) Ja (newLine()) Ja (über InputStreamReader/OutputStreamWriter)

6. Typische Fehler im Umgang mit BufferedReader und BufferedWriter

Fehler Nr. 1: Stream nicht geschlossen. Wenn Sie try-with-resources nicht verwenden oder close() nicht aufrufen, kann die Datei gesperrt bleiben und Daten werden eventuell nicht auf die Festplatte geschrieben. Verwenden Sie immer try-with-resources!

Fehler Nr. 2: Verwechslung von Text- und Binärdateien. Der Versuch, eine Binärdatei (".jpg", ".zip") über BufferedReader zu öffnen, führt zu „Kauderwelsch“ und sehr wahrscheinlich zu Fehlern. Für Binärdateien verwenden Sie InputStream/OutputStream.

Fehler Nr. 3: Keine Pufferung bei großen Datenmengen. Wenn zeichenweise gelesen oder geschrieben wird, läuft das Programm langsam. Verwenden Sie bei großen Dateien immer Pufferung.

Fehler Nr. 4: flush() nicht bei Bedarf aufgerufen. Wenn Daten sofort auf der Festplatte erscheinen sollen (z. B. fürs Logging), rufen Sie writer.flush() auf. Normalerweise erledigt das Schließen des Streams dies automatisch.

Fehler Nr. 5: Zeichencodierung wird nicht berücksichtigt. Wenn eine Datei mit falscher Codierung geöffnet wird, kann der Text falsch dargestellt werden (z. B. werden kyrillische Buchstaben zu „?“ oder zu unleserlichen Zeichen). Geben Sie immer explizit die benötigte Codierung an, wenn sie von der Systemcodierung abweicht (z. B. "UTF-8").

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION