Arbeiten mit Java-Dateien Teil 2: Wie man Dateien schnell und effizient schreibt

Wenn du unseren vorherigen Artikel Wie man Dateien in Java schnell und effizient liest gelesen hast, weißt du bereits, dass es viele verschiedene Möglichkeiten gibt, Dateien in Java zu lesen. Abhängig von der Art und Größe der Datei muss der richtige Ansatz gewählt werden, da es sonst passieren kann, dass das Einlesen der Datei unnötig lange dauert. Ähnlich gibt es viele Möglichkeiten, wie du Daten in Java in eine Datei schreiben kannst. Bestimmte Implementierungen können für eine Lösung besser geeignet sein als andere, je nach Art und Menge der Daten, die du in die Datei schreibst. Beim Schreiben in eine Datei müssen wir im Voraus wissen, ob wir unsere Daten in eine Text- oder Binärdatei schreiben werden. Im Fall einer Textdatei werden wir uns auf die Verwendung von Klassen wie Files, Writer, OutputStreamWriter, FileWriter, PrintWriter und BufferedWriter konzentrieren. Bei Binärdateien verwenden wir eher Klassen wie OutputStream, FileOutputStream, BufferedOutputStream, ByteOutputStream und DataOutputStream zum Schreiben. Es gibt auch externe Drittanbieter-Bibliotheken für die Arbeit mit Dateien, auf die wir heute jedoch nicht eingehen werden. Jede der oben genannten Klassen für die Arbeit mit Dateien hat ihren spezifischen Anwendungsfall. Im Allgemeinen gilt jedoch, dass wir zum Schreiben von Binärdaten die OutputStream-Klassen und für Textdaten die Writer-Klassen verwenden. Die meisten vordefinierten Klassen zum Lesen und Schreiben von Dateien in Java befinden sich in den Paketen java.io und java.nio.file. Mit der Einführung der neuen API-Schnittstelle für Dateien, Java NIO.2 (New I/O), hat sich die Situation noch weiter kompliziert, und selbst erfahrene Programmierer fragen häufig in Entwicklerforen, wie man effizient und schnell mit Dateien arbeitet. Das Schreiben von Daten in Java kann ebenfalls kompliziert werden. Daher konzentrieren wir uns in unserem Artikel auf zwei grundlegende Szenarien beim Schreiben von Daten, die 90 Prozent der Anwendungsfälle abdecken, und zwar:

  • Der einfachste Weg, einmal in Binär- und Textdateien zu schreiben.
  • Kontinuierliches Schreiben in eine Datei in Teilen

Java 7 brachte das lang erwartete NIO.2 File API die neben vielen Funktionen auch eine Hilfsklasse java.nio.file.Files mitbrachte, mit der man ein Byte-Array, eine Zeichenkette (String) oder eine Liste von Zeichenketten mit einem einzigen Befehl in eine Text-/Binärdatei schreiben kann.

Schreiben eines Byte-Arrays in eine Binärdatei

Ein Byte-Array kannst du wie folgt in eine Datei schreiben:

import java.nio.file.Files;
import java.nio.file.Path;

String fileName = "fileName.dat";
byte[] data = {72, 101, 108, 108, 111};
Files.write(Path.of(fileName), data);

Die Klasse Path (verfügbar seit Java 11) stellt eine Abstraktion einer Datei dar und enthält den Pfad zur Datei im Dateisystem.

Schreiben einer String-Variable in eine Textdatei

Das Schreiben einer String-Variable in eine Datei ab Java 11 ist sehr einfach:

import java.nio.file.Files;
import java.nio.file.Path;

String fileName = "fileName.txt";
String text = "Hello";
Files.writeString(Path.of(fileName), text);

Schreiben einer Liste von String-Variablen in eine Textdatei

Oft schreiben wir nicht nur eine Zeile in eine Textdatei, sondern mehrere Zeilen auf einmal. Mit dem folgenden Befehl kannst du eine Liste (bzw. genauer gesagt <Iterable ? extends CharSequence >) in eine Textdatei schreiben.

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

String fileName = "fileName.txt";
List<String> lines = new ArrayList<>(Arrays.asList("msg", "life"));
Files.write(Path.of(fileName), lines);

Schreiben in eine Textdatei mit String stream

Es gibt keine Methode, die direkt aus dem String-Stream in die Datei schreibt, aber es gibt einen kleinen Trick, mit dem Sie dies erreichen können:

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;

String fileName = "fileName.txt";
Stream<String> lines = new ArrayList<>(Arrays.asList("msg", "life")).stream();
Files.write(Path.of(fileName), (Iterable<String>) lines::iterator);

Wir verwenden die Methode Files.write(), ähnlich wie im vorherigen Beispiel, jedoch akzeptiert diese nur Argument Iterable< String> Der Stream selbst ist in Java 8 nicht iterierbar, da er nur einmal aufgerufen werden kann (wiederholte Aufrufe würden zu einer IllegalStateException führen). Kann daher nicht als Parameter verwendet werden. Es gibt jedoch das funktionale Interface Iterable, dessen einzige Methode einen Iterator zurückgibt. Daher können wir einen Verweis auf die Methode lines.iterator() übergeben, die ebenfalls einen Iterator als Iterable zurückgibt. V tejto časti sme sa zoznámili s pomocnými metódami triedy java.nio.file.Files. Diese Methoden sind für alle Anwendungsfälle geeignet, in denen die Daten, die wir in eine Datei schreiben möchten, vollständig im Speicher gespeichert sind und ihr Umfang nur einige Kilobyte beträgt. In anderen Fällen, insbesondere wenn wir wiederholt in Teile in eine Datei schreiben, ist es besser, eine der vordefinierten OutputStream-Klassen zu verwenden Im nächsten Abschnitt erklären wir, wie man das macht.

Schnelles Schreiben in Binärdateien mit BufferedOutputStream

Die Hauptklasse zum Schreiben von Binärdaten in eine Datei ist FileOutputStream, die es ermöglicht, Byte für Byte oder ein Byte-Array in die Datei zu schreiben. Das Schreiben einzelner Bytes ist eine sehr kostenintensive Operation, daher empfehlen wir, einen Puffer zu verwenden und das Schreiben in die Datei in Blöcken (sogenanntes Buffering) durchzuführen, was deutlich schneller ist. Auf den meisten Systemen beträgt die optimale Puffergröße 8 KB, weshalb Java diesen Wert als Standard verwendet. Für den gepufferten Schreibvorgang verwenden wir die Klasse BufferedOutputStream wie folgt.

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;

String fileName = "fileName.dat";
byte[] bytes;
try (FileOutputStream out = new FileOutputStream(fileName);
     BufferedOutputStream bout = new BufferedOutputStream(out)) {
    while((bytes = getOutputData()) != -1) {
        bout.write(bytes);
    }
}

In diesem Beispiel gibt die Methode getOuputData() (ihre Implementierung ist nicht wichtig) die Teile der Datei zurück, die sequentiell geschrieben werden.

Schnelles Schreiben in Textdateien mit BufferedWriter

Um Text in eine Datei zu schreiben, muss der Text zuerst in Binärdaten konvertiert werden. Für die Konvertierung von Zeichen in Bytes ist die Klasse OutputStreamWriter verantwortlich. Die Klasse FileWriter kombiniert FileOutputStream und OutputStreamWriter. Für schnelleres Schreiben von Dateien verwenden wir die Klasse BufferedWriter, die die Klasse FileWriter umschließt und einen 8 KB Puffer zusammen mit einem zusätzlichen Puffer für kodierte Zeichen ermöglicht.

import java.io.BufferedWriter;
import java.io.FileWriter;

String fileName = "fileName.txt";
try (FileWriter writer = new FileWriter(fileName);
     BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
    String line;
    while ((line = getLine()) != -1) {
        bufferedWriter.write(line);
    }
}

In diesem Beispiel gibt die Methode getLine() (deren Implementierung nicht wichtig ist) die Zeilen einer Datei zurück, die schrittweise geschrieben wird. In den vorherigen Beispielen haben wir die erstellten Streams nicht geschlossen. Nach Verlassen des Try-Blocks wird die close() -Methode automatisch aufgerufen, um beide Streams zu schließen, so dass dies nicht explizit angegeben oder benötigt wird.

Fazit

In diesem Artikel haben wir die häufigsten Szenarien für das Schreiben von Daten in eine Datei gezeigt. Kleinere Dateien, die wir im Speicher vorbereitet haben, können mit Methoden der Klasse Dateien Methoden geschrieben werden, und größere Dateien schreiben wir stückweise mit Hilfe des Caches. Wenn du ein Java Programmierer bist und nach Arbeit suchst, schau dir unsere Mitareiterbenefits an und reagiere auf die neuesten Stellenangebote.

Über den Autor

Jozef Wagner

Java Developer Senior

Viac ako 10 rokov programujem v Jave, momentálne pracujem v msg life Slovakia ako Java programátor senior a pomáham zákazníkom implementovať ich požiadavky do poistného softvéru Life Factory. Vo voľnom čase si rád oddýchnem v lese, prípadne si zahrám nejakú dobrú počítačovú hru.

Informieren Sie uns über sich