EMZETT.
Login

FileOutputStream

Kurz: Ein Byte-Stream zum Schreiben roher Binärdaten in eine Datei — das Gegenstück zu FileInputStream.

Genauer: Wie bei FileInputStream gilt: für reinen Text ist ein Writer (z. B. FileWriter/BufferedWriter) besser geeignet, da dieser die Zeichenkodierung übernimmt. FileOutputStream mit dem append-Konstruktor-Flag (new FileOutputStream(datei, true)) hängt an eine bestehende Datei an, statt sie zu überschreiben.

Im Detail

byte[] daten = { 0x48, 0x65, 0x6C, 0x6C, 0x6F }; // Bytes für "Hello"
 
try (FileOutputStream fos = new FileOutputStream("raw.bin")) {
    fos.write(daten);
} catch (IOException e) {
    System.err.println("Schreiben fehlgeschlagen: " + e.getMessage());
}
 
// Append-Modus - hängt an statt zu überschreiben
try (FileOutputStream log = new FileOutputStream("app.log", true)) {
    log.write("Neuer Eintrag\n".getBytes());
}

Wie bei FileInputStream gilt: FileOutputStream arbeitet auf roher Byte-Ebene, ohne irgendein Wissen über Zeichenkodierung. Für Textdateien ist ein Writer (FileWriter/BufferedWriter) meist die richtige Wahl, weil dieser automatisch String↔Byte-Konvertierung über eine konfigurierbare Zeichenkodierung (z. B. UTF-8) übernimmt — schreibt man Text stattdessen direkt über FileOutputStream mit .getBytes(), verlässt man sich implizit auf die Standard-Plattformkodierung, was zu Problemen führen kann, wenn die Datei später auf einem System mit anderer Standardkodierung gelesen wird. Explizit .getBytes(StandardCharsets.UTF_8) zu nutzen macht dieses Verhalten unabhängig von der Plattform.

Ohne Pufferung ist jeder write()-Aufruf potenziell teuer

FileOutputStream.write() schreibt standardmäßig UNGEPUFFERT — jeder einzelne Aufruf kann einen echten Betriebssystem-Systemaufruf auslösen. Wird in einer Schleife byteweise oder in vielen kleinen Häppchen geschrieben, summiert sich das schnell zu spürbarer Verlangsamung. Ein BufferedOutputStream als Wrapper sammelt Daten im Speicher und schreibt sie erst in größeren Blöcken tatsächlich zur Platte:

try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("groß.bin"))) {
    for (int i = 0; i < 100_000; i++) {
        bos.write(i); // landet im Puffer, nicht sofort auf der Platte
    }
} // beim Schließen wird der Restpuffer automatisch geleert ("geflusht")

flush() und wann er wichtig ist

flush() erzwingt, dass gepufferte Daten sofort tatsächlich geschrieben werden, statt im Puffer zu warten — relevant z. B. bei einem Log-Stream, der auch bei einem Programmabsturz mitten im Schreiben möglichst aktuelle Einträge auf der Platte haben soll. try-with-resources ruft beim Schließen automatisch close() auf, welches intern selbst noch einen finalen flush() durchführt — ein explizites flush() ist deshalb nur nötig, wenn Daten bereits VOR dem regulären Ende des try-Blocks sichtbar sein müssen.

Datei-Kanäle als Alternative für hohen Durchsatz

Für besonders performancekritischen Dateizugriff (z. B. große Log-Dateien mit hoher Schreibfrequenz) bietet java.nio.channels.FileChannel direkteren Zugriff auf den Betriebssystem-Puffer, inklusive der Möglichkeit, gezielt an eine beliebige Position zu schreiben, statt nur sequenziell ans Ende:

try (FileChannel kanal = FileChannel.open(Path.of("daten.bin"), StandardOpenOption.WRITE)) {
    ByteBuffer buffer = ByteBuffer.wrap("Text".getBytes());
    kanal.position(100); // an Byte-Position 100 springen
    kanal.write(buffer);
}

Für die allermeisten Anwendungsfälle ist das unnötiger Aufwand — FileOutputStream/Files.write() reichen völlig aus, FileChannel lohnt sich erst bei sehr spezifischen Performance- oder Positionierungs-Anforderungen.

Siehe auch: I/O Streams, FileInputStream, Write Files