09.01 Streams (Datenfluss) in Java
Wie in jeder anderen Programmiersprache auch, können Sie in Java mit Datenströmen (Streams) arbeiten, welche Sie im Package java.io
finden. Typischerweise werden Daten aus oder in einer Quelle (z. B. eine Datei oder Benutzereingaben von der Konsole) von einem Stream Byte für Byte gelesen oder geschrieben. Dabei gibt es für unterschiedliche Quellen auch unterschiedliche Streams. Diese werden wir Ihnen in diesem Kapitel (09. Input / Output (IO)) näher bringen.
In Java unterscheidet man grundsätzlich zwischen zwei verschiedenen Arten von Streams. Einmal die „richtigen“ Streams, die binäre, also für Menschen nicht, oder nur schwer lesbare Quellen verarbeiten können. Dazu gehört z. B. der ByteArrayInputStream
, der Daten in Form von byte
-Arrays empfängt, oder der FileOutputStream
, der binäre Daten auf eine Festplatte schreiben kann. Solche binären Streams erkennen Sie daran, dass der InputStream
(lesen) bzw. der OutputStream
(schreiben) irgendwo in der Vererbungshierarchie des Streams auftaucht.
Auf der anderen Seite stehen die Reader
und Writer
, die für das Lesen und Schreiben von Text zuständig sind. Hierzu gehört z. B. der FileWriter
, welcher normalen Text in eine Datei schreiben kann, oder der InputStreamReader
, der einen binären InputStream
in einen Reader
konvertiert. Solche Streams erkennen Sie daran, dass deren Vererbungshierarchie den Reader
(lesen) oder der Writer
(schreiben) enthält.
Beachten Sie, dass Reader
und Writer
wirklich nur in Textdateien schreiben bzw. aus ihnen lesen sollten, wohingegen InputStreams
und OutputStreams
zwar primär für binäre Daten gedacht sind, grundsätzlich aber alles – also auch für Menschen lesbaren Text – verarbeiten können.
Wie Sie sicherlich schon festgestellt haben, gibt es für jeden „Schreiber“ (egal ob binär oder Textdatei) auch ein entsprechendes Gegenstück, einen „Leser“. Wenn Sie etwas lesen können (z. B. aus einer Datei über den FileInputStream
), können Sie es auch wieder schreiben (z. b. in eine Datei über den FileOutputStream
). Meistens werden hierbei lediglich die Schlüsselwörter Input und Output bzw. Reader und Writer im Klassennamen vertauscht.
Zum Abschluss dieses Einführungskapitels werden wir einen Text über einen FileWriter
in eine Datei schreiben, und diesen über einen FileReader
wieder auslesen. Eine simple Erklärung dazu finden Sie in Form von Kommentaren direkt im Quelltext.
package de.jbb.io; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class SimpleFileIO { public static void main(String[] args) { // Die Writer und Reader deklarieren FileWriter fw = null; FileReader fr = null; // try-catch-Block, da Filesystem-Zugriffe zu Fehlern // führen können. try { // Den Writer initialisieren. Bei der Initialisierung // wird ihm mitgeteilt, wohin er schreiben soll fw = new FileWriter("C:/javaio.txt"); // Schreiben eines Textes fw.write("Ein Simpler Text als Beispiel "); fw.write("aus dem Java Blog Buch"); } catch (IOException e) { e.printStackTrace(); } finally { // WICHTIG: Streams nach der Verwendung wieder schließen! // Am Besten im finally-Block fw.close(); } try { // Die Textdatei wurde unter "C:\" mit dem Namen // "javaio.txt" erstellt und befüllt. Jetzt können Sie // sie auch wieder auslesen. Hierzu initialisieren Sie // den Reader. Diesem wird auch mitgegeben, aus welcher // Datei er lesen soll. fr = new FileReader("C:/javaio.txt"); // Die Methode read eines jeden Streams liest Zeichen // für Zeichen bzw. Byte für Byte einer Quelle. Sobald // keine Daten mehr vorhanden sind, wird -1 ausgegeben. // Wir lesen also solange, bis read -1 zurück gibt. In // der Schleife wird das Gelesene ausgegeben. for (int i = fr.read(); i != -1; i = fr.read()) { // Den Integer noch in ein Zeichen konvertieren ... System.out.print((char)i); } } catch (IOException e) { e.printStackTrace(); } finally { // FileReader schließen fr.close(); } } }
Hallo,
ich würde dringend empfehlen fw.close() und fr.close() in einen finally-block zu packen. Auch wenn das Beispiel nur zu Demo-Zwecken dient…
Cheers,
Tino
Hallo Tino,
natürlich sollten Verbindungen – egal ob auf das Filesystem oder eine Datenbank – immer im finally-Block geschlossen werden. In diesem Beispiel habe ich darauf verzichtet, da die Streams ohnehin auf jeden Fall bei der Beendigung des Programms und somit mit der Main-Methode geschlossen werden würden.
Aber da sich evtl. der Eine oder Andere ein Beispiel daran nehmen, bzw. den Code erweitern könnte, habe ich den betreffenden Ausschnitt dennoch ausgebessert.
Noch eine kleine Anmerkung meinerseits: Es ist zwar wichtig, dass Streams über
close()
wieder ordentlich geschlossen werden, aber lange nicht so dramatisch wie eine nicht sauber geschlossene Verbindung zu einer Datenbank. Aber hierzu später mehr.Gruß
Stefan
hallo
ich hätte mal ne frage gibt es auch „schreiber“ die nich geschlossen werden müssen und einfach „hardcore“ ihre daten schreiben ?
gruß fabian
Hallo Fabian,
auch solche
OutputStreams
gibt es. Hierzu gehört bspw. derjava.io.ByteArrayOutputStream
beziehungsweise fast alle Streams, die intern keinen Puffer verwenden (hier gibt es aber meist andere gute Gründe diese Streams zu schließen).Wenn Sie sich uneinig sind, ob ein Stream geschlossen werden muss oder nicht, sollten Sie einen Blick auf die Dokumentation und in den Quellcode des Streams werfen (siehe Kapitel 00.07.01 Wie lerne ich programmieren (Java) im Abschnitt Studieren Sie Code). Generell können Sie aber nichts falsch machen, wenn Sie Streams nach ihrer Verwendung ordnungsgemäß schließen.
Gruß
Stefan