CodeGym /Kurse /JAVA 25 SELF /Lesen und Schreiben binärer Dateien: InputStream, OutputS...

Lesen und Schreiben binärer Dateien: InputStream, OutputStream

JAVA 25 SELF
Level 36, Lektion 2
Verfügbar

1. Einleitung

Fangen wir mit dem Einfachsten an: Worin unterscheidet sich eine Binärdatei von einer Textdatei? Eine Textdatei ist eine Datei, die man in einem normalen Editor öffnen und Buchstaben, Ziffern, Leerzeichen und andere Zeichen sehen kann. Zum Beispiel my_notes.txt oder poem.txt.

Binärdatei – das ist eine Datei, die keinen Text, sondern beliebige Bytes enthält. Das kann ein Bild (.jpg, .png), Musik (.mp3), ein Archiv (.zip), eine ausführbare Datei (.exe), ein Video (.mp4), eine Datenbankdatei und so weiter sein. Wenn Sie eine solche Datei in einem Editor öffnen, sehen Sie etwas wie ÿØÿà oder eine ganze Wand unverständlicher Zeichen. Das ist normal! Der Computer „versteht“ nur Bytes – für ihn sind Text, Bild und Video einfach eine Folge von Bytes. In Textdateien lassen sich diese Bytes als Zeichen interpretieren, in Binärdateien sind es „rohe“ Daten, die nicht für das menschliche Lesen bestimmt sind.

Hauptklassen für die Arbeit mit Binärdateien

In Java werden für die Arbeit mit Binärdateien Bytestreams verwendet:

  • InputStream – Basisklasse zum Lesen von Bytes.
  • OutputStream – Basisklasse zum Schreiben von Bytes.

Für Dateien gibt es konkrete Implementierungen:

  • FileInputStream – liest Bytes aus einer Datei.
  • FileOutputStream – schreibt Bytes in eine Datei.

Wenn Sie von FileReader und FileWriter gehört haben: Sie arbeiten mit Zeichen und eignen sich nur für Text. Für Binärdateien verwenden Sie ausschließlich InputStream/OutputStream und deren Unterklassen.

2. Binärdateien lesen

Byteweises Lesen

Die einfachste Methode ist, die Datei Byte für Byte zu lesen. Das ist anschaulich, aber sehr langsam.

try (FileInputStream in = new FileInputStream("image.jpg")) {
    int b;
    while ((b = in.read()) != -1) {
        // b ist eine Zahl von 0 bis 255 (ein Byte), -1 bedeutet Dateiende
        // Man kann das Byte verarbeiten, z. B. die Summe aller Bytes berechnen
    }
}

Die Methode read() gibt das nächste Byte als int (von 0 bis 255) zurück, und wenn die Datei zu Ende ist, gibt sie -1 zurück. Byteweises Lesen nutzt man normalerweise nur, wenn etwas sehr Spezifisches nötig ist (z. B. die Struktur einer Datei zu analysieren).

Blockweises Lesen (mit Puffer)

Byteweise zu lesen ist, als würde man für jeden Apfel einzeln in den Laden gehen. Viel effizienter ist es, gleich eine ganze Tüte zu nehmen! In Java gibt es dafür die Methode read(byte[] buffer), die ein Array mit Bytes aus der Datei füllt.

try (FileInputStream in = new FileInputStream("image.jpg")) {
    byte[] buffer = new byte[4096]; // Puffer mit 4 KB
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        // buffer enthält bytesRead Bytes aus der Datei
        // Diese Bytes kann man weiterverarbeiten, z. B. irgendwohin speichern
    }
}

Die Methode read(buffer) liefert zurück, wie viele Bytes tatsächlich gelesen wurden (kann kleiner als die Puffergröße sein, insbesondere beim letzten Lesevorgang). Dieser Ansatz ist deutlich schneller, weil es weniger Zugriffe auf das Laufwerk gibt.

Beispiel: Datei kopieren

Schreiben wir ein einfaches Programm, das eine beliebige Binärdatei (z. B. ein Bild) von einem Ort an einen anderen kopiert.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BinaryCopyExample {
    public static void main(String[] args) {
        String source = "cat.jpg";
        String dest = "cat_copy.jpg";

        try (FileInputStream in = new FileInputStream(source);
             FileOutputStream out = new FileOutputStream(dest)) {

            byte[] buffer = new byte[8192]; // 8 KB – eine optimale Größe für die meisten Aufgaben
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            System.out.println("Kopieren abgeschlossen!");
        } catch (IOException e) {
            System.out.println("Fehler beim Kopieren: " + e.getMessage());
        }
    }
}

Ganz einfach: Wir lesen Blöcke aus der Quelldatei und schreiben sie sofort in die Zieldatei. Diese Methode funktioniert mit allen Dateitypen: Bildern, Archiven, Videos.

3. Binärdateien schreiben

Ein Byte-Array schreiben

Wenn Sie ein Byte-Array haben (z. B. aus dem Netzwerk erhalten oder im Programm erzeugt), können Sie es so in eine Datei schreiben:

byte[] data = new byte[] {1, 2, 3, 4, 5}; // Beispiel-Array

try (FileOutputStream out = new FileOutputStream("data.bin")) {
    out.write(data); // schreibt das gesamte Array in die Datei
}

Die Methode write(byte[]) schreibt alle Bytes des Arrays. Sie können auch nur einen Teil des Arrays schreiben: out.write(data, offset, length).

Datei in Teilen schreiben (z. B. beim Kopieren)

Wie beim Lesen verwendet man in der Regel einen Puffer:

try (FileInputStream in = new FileInputStream("source.bin");
     FileOutputStream out = new FileOutputStream("dest.bin")) {

    byte[] buffer = new byte[4096];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}

Hier schreiben wir alles, was wir aus der Datei lesen, direkt in eine andere Datei. Solcher Code findet sich häufig in Packprogrammen, Downloadern, Bildverarbeitern usw.

4. Nützliche Besonderheiten

Warum sollte man Reader/Writer nicht für Binärdateien verwenden?

Reader und Writer arbeiten mit Zeichen (char), nicht mit Bytes. Sie konvertieren Bytes automatisch gemäß der Zeichenkodierung (z. B. UTF-8) in Zeichen. Das ist für Text praktisch, für Binärdateien jedoch brandgefährlich!

Wenn Sie versuchen, ein Bild mit FileWriter zu schreiben, erhalten Sie eine beschädigte Datei, die sich nicht öffnen lässt. Merken Sie sich: Für alle nicht-textuellen Dateien ausschließlich InputStream/OutputStream verwenden!

Wichtige Unterschiede und Besonderheiten beim Umgang mit Binärdateien

  • Puffergröße: Ein zu kleiner Puffer verlangsamt die Arbeit (zu viele Zugriffe auf das Laufwerk), ein zu großer belegt unnötig Speicher. 4–16 KB – normalerweise optimal.
  • Fehlerbehandlung: Behandeln Sie stets IOException – die Datei könnte nicht existieren, gesperrt sein oder der Speicherplatz auf dem Laufwerk könnte ausgehen.
  • Streams schließen: Verwenden Sie try-with-resources – das garantiert das Schließen der Dateien selbst bei Fehlern.
  • Überschreiben einer Datei: Wenn Sie eine Datei über new FileOutputStream("file.bin") öffnen, wird sie überschrieben. Um an das Ende anzuhängen, verwenden Sie den Konstruktor mit dem Parameter append = true.
  • Zugriffsrechte: Wenn das Programm die Datei nicht öffnen kann, prüfen Sie die Lese-/Schreibrechte.
  • readAllBytes(): Ermöglicht, die gesamte Datei in einem Schritt in ein Byte-Array zu lesen. Für große Dateien – nicht verwenden, um nicht den gesamten Speicher zu belegen!

5. Typische Fehler beim Umgang mit Binärdateien

Fehler Nr. 1: Verwendung von FileReader/FileWriter für Binärdateien. Das führt zur Datenkorruption, weil diese Klassen Bytes in Zeichen und zurück konvertieren – für Bilder, Archive usw. ist das katastrophal.

Fehler Nr. 2: Rückgabewert von read() ignoriert. Die Methode read(byte[]) kann weniger Bytes lesen, als angefordert – insbesondere im letzten Block. Verwenden Sie stets den Rückgabewert, um zu wissen, wie viele Bytes tatsächlich verarbeitet wurden.

Fehler Nr. 3: Stream nicht geschlossen. Wenn die Datei nicht geschlossen wird, kann sie gesperrt bleiben und die Daten werden möglicherweise nicht vollständig geschrieben (insbesondere beim Schreiben!). Verwenden Sie try-with-resources.

Fehler Nr. 4: Die gesamte Datei ohne Blick auf die Größe in den Speicher laden. Für große Dateien führt das zu OutOfMemoryError. Verwenden Sie einen Puffer und lesen Sie in Teilen.

Fehler Nr. 5: Ausnahmen werden nicht behandelt. Die Arbeit mit Dateien kann stets zu Fehlern führen: Datei nicht gefunden, keine Rechte, Laufwerk voll. Vergessen Sie nicht, IOException zu behandeln.

1
Aufgabe
JAVA 25 SELF, Level 36, Lektion 2
Gesperrt
Extraktion eines wertvollen Fragments aus einem Binärstrom 🧪
Extraktion eines wertvollen Fragments aus einem Binärstrom 🧪
1
Aufgabe
JAVA 25 SELF, Level 36, Lektion 2
Gesperrt
Berechnung des energetischen Potenzials eines Artefakts ⚛️
Berechnung des energetischen Potenzials eines Artefakts ⚛️
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION