Ein- und Ausgabe über Streams

 

Duke surft...

Programme müssen Daten von unterschiedlichen Datenquellen lesen können und sie müssen Daten auf unterschiedliche Datenziele schreiben können. Hierbei kann es sich um die unterschiedlichsten Quellen und Ziele handeln:

  • Konsoleneingabe
  • Konsolenausgabe
  • Dateien auf dem Rechner
  • Dateien auf entfernten Rechnern (z. Bsp. dem Internet)
  • Quellen und Senken auf anderen Rechnern

 

 Alle Ein- und Ausgaben in Java laufen "stromorientiert" ab, sie folgen dem "Stream-Konzept".

Definition

Ein Stream ist die Verbindung zwischen einer Datenquelle und einem Datenziel.

Die Verbindung erfolgt immer in nur eine Richtung (von der Quelle zum Ziel).

Um Eingaben in Java zu erhalten muß man einen Strom öffnen der mit einer Datenquelle verbunden ist und anschließend die Daten sequentiel lesen.

Für Ausgaben muß man einen Datenstrom zu einem Datenziel öffnen, dann die Daten sequentiell schreiben.

Dieses Konzept ist einem Wasserschlauch ähnlich: Sie benötigen jemand der das Wasser einspeißt und einen anderen Konsumenten der das Wasser wieder entnimmt.

Kategorien von Streams

In Java werden zwei Kategorien unterschieden:

  • Character-Streams: Transportieren 16 Bit Daten. Sie arbeiten mit dem Javatyp char der Unicodezeichen darstellt
    • Ihre Basisfunktionalität wird durch die abstrakten Klassen Reader und Writer bereitgestellt
  • Byte-Stream: Transportieren 8 Bit Daten. Sie arbeiten mit dem Javatyp byte.

Wichtig: Alle Klassen die von diesen vier Basisklassen spezialisiert werden haben als Endung den Namen der abstrakten Klasse!

Bsp.: Bei der Klasse FileWriter handelt handelt es sich um einen Ausgabestrom auf Dateien.

Bsp.: Bei der Klasse FileWriter handelt handelt es sich um einen Ausgabestrom auf Dateien.

Frage: Verwaltet er Bytes oder Zeichen?

 java.nio und mehr...

Duke auf Sofa

Seit der Version JDK 1.4 gibt es in Java das Paket java.nio mit einer Reihe neuer Klassen. Diese Paket ist nicht Gegenstand der Vorlesung.

In diesem Abschnitt werden nur einige wenige, ausgewählte Methoden der Klassen vorgestellt. Bitte benutzen Sie die Hyperlinks zur Java API Dokumention und einen vollständigen Überblick zu bekommen.

Die Klasse File

Viele Operationen werden auf Dateien ausgeführt.  Die Klasse File im Paket java.io erlaubt das manipulieren von Dateien und Verzeichnissen.

Im Beispiel der Klasse s2.io.DateiTest (github) kann man sehen wie man Verzeichnisse anlegt und ausliest und neue Dateien anlegt. Methoden zum manipulieren und Auslesen von Dateiattributen sind in der Java Dokumentation zur Klasse File beschrieben.

package s2.io;

/**
* @author s@scalingbits.com
*/
import java.io.File;
import java.io.IOException;

public class DateiTest {
/**
* Hauptprogamm
* @param args wird nicht verwendet...
*/
public static void main(String[] args) {
String d ="testDir";
String f1 = "datei1.txt";
String f2 = "datei2.txt";
File dir = new File(d);
File file1 = new File(d + "/" + f1);
File file2 = new File(d + "/" + f2);

if (dir.exists())
System.out.println("Verzeichnis " + d + " existiert bereits");

dir.mkdir();
try {
file1.createNewFile();
System.out.println("Datei wurde angelegt in : "
+ file1.getAbsolutePath() );
file2.createNewFile();
System.out.println("Datei wurde angelegt in : "
+ file2.getAbsolutePath() );

System.out.println("Dateien im Verzeichnis "
+ dir.getAbsolutePath());
String[] alleDateien = dir.list();
for ( String f : alleDateien)
System.out.println("* " + f);
} catch (IOException ex) {
System.out.println("Probleme im IO Subsystem. Scotty beam me up!");
}
}
}

 

Übung: Erweitern Sie Anwendung so, dass die Dateien und das Verzeichnis wieder gelöscht werden!

Frage: Welche Methoden brauche ich hierfür?

Sie werden doch nicht auf diesen Hyperlink klicken: github s2.io.DateiTestLoesung.java

Einführung in Reader- und Writerklassen

Die Reader und Writer Klassen in Java erlauben es Zeichen-Stöme zu verwalten.

Die spezialisierten Klassen FileReader und FileWriter haben Konstruktoren in denen man als Quelle bzw. Ziel eine Datei angeben kann:

Ihre Oberklassen InputStreamReader und OutputStreamWriter haben Konstruktoren die die Konvertierung von Streams erlauben.

Hiermit kann man einen Byte-Stream (inputStream)  in einen Zeichen-Stream (Reader) verwandeln. Dies ist nützlich wenn man eine Binärdatei (Byte-Stream) in eine Datei (Zeichen-Stream) schreiben möchte.

 

Java Reader und Writer Klassen 

Im folgenden Beispiel wird gezeigt wie man einen Zeichen-Stream von der Konsole lesen kann und ihn dann in eine Datei schreibt. Die Java Klasse System bietet mit dem Attribute System.in Zugriff auf einen InputStream mit Konsoleneingaben und auf Konsolenausgaben (System.out).

/*
* Ein Beispielprogramm welches von der Konsole liest und in eine Datei schreibt
*/
package s2.io;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
/**
*
* @author s@scalingbits.com
*/
public class SchreibInDatei {
/**
* Hauptprogamm
* @param args wird nicht verwendet...
*/
public static void main(String[] args) {
try {
String f = "scalingbits.txt";
File datei = new File(f);
Reader rein = new InputStreamReader(System.in);
Writer raus = new FileWriter(datei);
System.out.println("Der Text der jetzt eingegeben wird, wird in " +
"der Datei " + f + " gespeichert");
System.out.println("Abschliesen mit Strg-Z oder Ctrl-Z");
System.out.println("Abschliesen Auf Unix/Linux mit Ctrl-D");
umkopieren(rein,raus);
} catch (IOException ex) {
System.out.println("Probleme im IO Subsystem. Scotty beam me up");
}

}
/**
* Umkopieren zwischen zwei Streams
* @param r Eingabestream
* @param w Ausgabestream
* @throws IOException
*/
public static void umkopieren(Reader r, Writer w) throws IOException{
int c;
while ((c= r.read()) != -1 ) {
w.write(c);
}
r.close();
w.close();
}
}

 

Übung: Erweitern Sie Anwendung so, dass sie die geschriebene Datei wieder ausliest und auf der Konsole druckt. Die Methoden umkopieren() kann man wiederverwenden...

Frage: Welche Klassen brauche ich hierfür?

Sie werden doch nicht auf diesen Hyperlink klicken: github s2.io.SchreibInDateiLoesung.java

 Gepufferte Reader- und Writer Klassen

Das Lesen bzw. Schreiben einzelner Bytes oder Zeichen vom Netzwerk oder von Dateien ist oft sehr ineffizient. Zum effizienteren Behandlung größerer Datenmengen stellt Java die Klassen BufferedReader und BufferedWriter zur Verfügung. Diese Klassen speichen die Daten zwischenzeitlich in einem Puffer und Lesen bzw. Schreiben die Daten in größeren Blöcken. Diese gepufferten Klassen müssen mit den elementaren Klassen verkettet werden. Deshalb haben sie die folgenden Konstruktoren:

Sie Verfügen über zusatzliche Methoden zum gepufferten Lesen und Schreiben

 Nutzt man diese Klassen im Beispiel, gibt es eine Reihe von Veränderungen:

  • mit BufferedReader.readLine() erhält man eine Zeichenkette als Ergebnis (oder einen Nullzeiger!)
  • man sollte beim Schreiben den Zeilenimbruch selbst einfügen

Die Klasse SchreibInDateiGepuffered

/*
* Eins Beispielprogramm welches von der Konsole liest und in eine Datei schreibt
*/
package s2.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

/**
*
* @author s@scalingbits.com
*/
public class SchreibInDateiGepuffered {
/**
* Hauptprogamm
* @param args wird nicht verwendet...
*/
public static void main(String[] args) {
try {
String f = "scalingbits.txt";
File datei = new File(f);
BufferedReader rein = new BufferedReader(
new InputStreamReader(System.in));
BufferedWriter raus = new BufferedWriter(
new FileWriter(datei));
System.out.println("Der Text der jetzt eingegeben wird, wird in " +
"der Datei " + f + " gespeichert");
System.out.println("Abschliesen mit Strg-Z oder Ctrl-Z");
System.out.println("Abschliesen Auf Unix/Linux mit Ctrl-D");
umkopieren(rein,raus);

rein = new BufferedReader(
new FileReader(f));
raus = new BufferedWriter(
new OutputStreamWriter(System.out));

System.out.println("Ausgabe des in Datei " + f
+ "gespeichertem Texts");
umkopieren(rein,raus);
} catch (IOException ex) {
System.out.println("Probleme im IO Subsystem. Scotty beam me up");
}

}
/**
* Umkopieren zwischen zwei Streams
* @param r Eingabestream
* @param w Ausgabestream
* @throws IOException
*/
public static void umkopieren(BufferedReader r, BufferedWriter w) throws IOException{
String z;
while ((z= r.readLine()) != null) {
w.write(z);
w.newLine();

}
r.close();
w.close();
}
}

Die Datei SchreibInDateiGepuffered in github.

Behandlung von Byte-Strömen

Von den Byte-Streams werden hier nur wenige ausgewählte vorgestellt

Hierarchie Byte-Ströme

Die abstrakte Basisklasse InputStream hat für die Eingabe von Byte-Streams unter anderem die folgenden Methoden:

Die abstrakte Basisklasse OutputStream besitzt ähnliche Methoden

DataInput- und DataOutputStream

Die Klassen DataInputStream und DataOutputStream erlauben das Lesen und Schreiben von Java-Basistypen Ihre Konstruktoren arbeiten mit den Basisklassen:

Diese Klassen stellen dann typspezifische Methoden mit der folgenden Notation zur Verfügung. Zum Beispiel DataOutputStream:

  • public xxx readxx(): Lies den Typ xxx und gib ihn aus

Serialisierung und Deserialisierung von Objekten

Die Klassen ObjectInputStream und ObjectOutputStream erlauben es Objekte aus dem Hauptspeicheer in einen Stream zu schreiben.

Sie wandeln den systemabhängigen Zustand eines Objects in eine neutrale Binärdarstellung in einem Datenstrom um. Diesen Vorgang nennt man Serialisierung in Java. Den umgekehrten Vorgang nennt man Deserialiserung.

Wichtig: Es könne nur Objekte serialiert werden die die Schnittstelle Serializable implementieren. Diese Schnittstelle verlangt es nicht Methoden zu implementieren.

Alle referenzierten, serialisierbaren Objekte werden auch serialisiert.

Möchte man Instanzvariablen nicht serialisieren, muss man sie mit Schlüsselwort transient kennzeichnen.

Die beiden Klassen ObjectInputStream und ObjectOutputStream haben Konstruktoren, die es erlauben aus bzw. in andere Ströme zuschreiben:

In geöffnete Ströme vom Type ObjectInputStream kann man mit der folgenden Methoden lesen:

Ähnlich kann in ObjectOutputStream mit der folgenden Methode ein Objekt schreiben

 

Weitere Klassen

 Das java.io Paket hat noch viele weiter Klassen. Hier eine kurze Liste der interessantesten Klassen

Übungen ( Streams)

Vokalverschiebung

Ersetzen Sie alle Vokale in einer Textdatei durch bestimmte andere und erzeugen Sie eine neue Datei mit den verschobenen Vokalen.

Eine Musterlösung ist in github zu finden.

Lernziele

Am Ende dieses Blocks können Sie:

  • ... in Java aus Datein lesen und in Dateien schreiben
  • ... erkennen ob eine Klasse mit Zeichen- oder Byteströmen agiert
  • ... Ströme verketten
  • ... Zeichen, Basistypen und Objekte in Ströme lesen sowie schreiben

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten: Fragen zur Streams

Zur Umfrage

QR Code für Umfrage