• Keine Ergebnisse gefunden

5.4.3 Ströme zur Ein- und Ausgabe Ein- und Ausgabe von Daten wird heutzutage meist durch Ströme modelliert.

N/A
N/A
Protected

Academic year: 2022

Aktie "5.4.3 Ströme zur Ein- und Ausgabe Ein- und Ausgabe von Daten wird heutzutage meist durch Ströme modelliert."

Copied!
5
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

08.02.2007 © A. Poetzsch-Heffter, TU Kaiserslautern 691

5.4.3 Ströme zur Ein- und Ausgabe

Ein- und Ausgabe von Daten wird heutzutage meist durch Ströme modelliert.

Begriffsklärung: (Datenstrom)

Ein Strom ist eine potentiell unendliche Folge von Daten. Er wird von einer oder mehrerer Quellen mit Daten versorgt und erlaubt es, diese Daten der Reihe nach aus dem Strom herauszulesen.

Das Ende eines Stromes wird durch ein spezielles Datum (in Java ist das -1) markiert.

Sowohl beim Schreiben in einen Strom als auch beim Lesen aus einen Strom kann es zu Verzögerungen kommen:

- beim Lesen, weil augenblicklich kein Zeichen vorhanden, der Strom aber noch nicht zu Ende ist;

- beim Schreiben, weil ggf. kein Platz im Strom vorhanden ist.

Die Verzögerungen führen zu einer Blockierung der ausgeführten Methode.

08.02.2007 © A. Poetzsch-Heffter, TU Kaiserslautern 692

Bemerkung:

• Stromklassen sind in diesem Kontext interessant:

- als wichtige Programmierschnittstelle

- als Beispiel für Typhierarchien und eigenständige Bausteine

- als Beispiel für Komposition von Bausteinen

• Vergleiche auch unendliche Listen in ML (Folie 215).

Einführung in Ströme

Wir betrachten zunächst Ströme zum Lesen:

interface CharEingabeStrom { int read() throws IOException;

}

Diese Schnittstelle abstrahiert von der Quelle aus der gelesen wird. Mögliche Quellen:

1. Datenstruktur wie Feld, Liste, String.

2. Datei 3. Netzwerk

4. Standardeingabe, d.h. interaktive vom Anwender 5. andere Programme

6. andere Ströme

Wir betrachten hier die Fälle 1, 2 und 6.

Lesen aus einer Datenstruktur:

Wir betrachten das schrittweise Lesen der Zeichen eines Strings:

public class StringLeser

implements CharEingabeStrom { private char[] dieZeichen;

private int index = 0;

public StringLeser( String s ) { dieZeichen = s.toCharArray();

}

public int read() {

if( index == dieZeichen.length ) return -1;

else return dieZeichen[index++];

} }

Die Quelle des Stroms wird dem Stromkonstruktor übergeben.

Zusammenbauen von Strömen:

Wir betrachten zunächst zwei Stromklassen, die aus anderen Strömen lesen und diese ändern.

public class GrossBuchstabenFilter

implements CharEingabeStrom { private CharEingabeStrom eingabeStrom;

public GrossBuchstabenFilter(

CharEingabeStrom cs ) { eingabeStrom = cs;

}

public int read() throws IOException { int z = eingabeStrom.read();

if( z == -1 ) { return -1;

} else {

return Character.toUpperCase( (char)z );

} } }

Der Konstruktor nimmt einen beliebigen CharEingabeStrom als Quelle:

Subtyping at its best!

(2)

08.02.2007 © A. Poetzsch-Heffter, TU Kaiserslautern 695

public class UmlautSzFilter

implements CharEingabeStrom { private CharEingabeStrom eingabeStrom;

private int puffer = -1;

public UmlautSzFilter( CharEingabeStrom cs ){

eingabeStrom = cs;

}

public int read() throws IOException { if( puffer != -1 ) {

int z = puffer;

puffer = -1;

return z;

} else {

int z = eingabeStrom.read();

if( z == -1 ) return -1;

switch( (char)z ) {

case '\u00C4': puffer = 'e'; return 'A';

case '\u00D6‚: puffer = 'e'; return 'O';

case '\u00DC': puffer = 'e'; return 'U';

case '\u00E4': puffer = 'e'; return 'a';

case '\u00F6': puffer = 'e'; return 'o';

case '\u00FC': puffer = 'e'; return 'u';

case '\u00DF': puffer = 's'; return 's';

default: return z;

} } } }

08.02.2007 © A. Poetzsch-Heffter, TU Kaiserslautern 696

Folgendes Programm zeigt Zusammenbau und Anwendung von Strömen:

public class Main {

public static void main(String[] args) throws IOException { String s = new String(

"\u00C4neas opfert den G\u00D6ttern "

+ "edle \u00D6le,\nauf "

+ "da\u00DF \u00FCberall "

+ "das \u00DCbel sich \u00E4ndert.");

CharEingabeStrom cs;

cs = new StringLeser( s );

cs = new UmlautSzFilter( cs );

cs = new GrossBuchstabenFilter( cs );

int z = cs.read();

while( z != -1 ) {

System.out.print( (char)z );

z = cs.read();

}

System.out.println("");

} }

Adaption von Strömen:

Adaption bedeutet in der Objektorientierung meist das Anpassen einer Schnittstelle an die Bedürfnisse eines Anwenders.

Als kleines Beispiel einer Adaption betrachten wir die typmäßige Anpassung der Klasse FileReader aus java.io an CharEingabeStrom:

FileReader CharEingabeStrom

DateiLeser

Da FileReader eine Methode read mit der gleichen Signatur und Bedeutung wie in CharEingabeStrom bereitstellt, reicht folgende fast triviale Adaptionsklasse:

public class DateiLeser

extends FileReader

implements CharEingabeStrom { public DateiLeser( String s )

throws IOException { super(s);

} }

Javas Stromklassen

Stromklassen werden nach den Datentypen, die sie verarbeiten, und ihre Quellen bzw. Senken klassifiziert.

Stromklassen sind wichtige programmiertechnische Hilfsmittel und ihre Hierarchien ein gutes Beispiel für eigenständige Bausteine.

Die Reader-/Writer-Klassen aus dem Paket java.io verarbeiten char-Ströme; die Input-/Output-Strom- klassen verarbeiten byte-Ströme.

Die Reader-Klassen unterstützen:

• das Lesen einzelner Zeichen: int read() ;

• das Lesen mehrerer Zeichen aus der Quelle und Ablage in ein char-Feld: int read(char[]) u. ä.;

• das Überspringen einer Anzahl von Zeichen der Eingabe: long skip(long) ;

• die Abfrage, ob der Strom für das Lesen des nächsten Zeichens bereit ist ;

• das Schließen des Eingabestroms: void close();

• Methoden zum Markieren und Zurücksetzen des Stroms.

(3)

08.02.2007 © A. Poetzsch-Heffter, TU Kaiserslautern 699

Die Writer-Klassen unterstützen:

• das Schreiben einzelner Zeichen:

void write( int ) ;

• das Schreiben mehrerer Zeichen eines char- Feldes: void write(char[]) u. ä.;

• das Schreiben mehrerer Zeichen eines String:

void write(String) u. ä.;

• die Ausgabe ggf. im Strom gepufferter Zeichen:

void flush() ;

• das Schließen des Ausgabestroms: void close().

Die genannten Methoden lösen möglicherweise eine IOException aus.

Die vonInputStreambzw.OutputStreamabge- leiteten Klassen leisten Entsprechendes für Daten vom Typ byte.

Reader-/Writer-Klassen:

Die Reader-Klassen unterscheiden sich im Wesentlichen durch ihre Quelle:

08.02.2007 © A. Poetzsch-Heffter, TU Kaiserslautern 700

Reader-Klasse Quelle Bemerkung InputStreamReader InputStream

FileReader byte-Strom aus Datei

BufferedReader Reader puffernd; kann LineNumberReader Reader zeilenweise lesen PipedReader PipedWriter

FilterReader Reader

PushBackReader Reader Methodeunread CharArrayReader char[]

StringReader String

InputStreamR PipedR

Reader

CharArrayR StringR

BufferedR FilterR

FileR LineNumberR PushBackR

Bemerkung:

Die Konstruktoren ermöglichen das Zusammenhängen von Strömen; hier am Beispiel eines Konstruktors der Klasse PrintWriter:

public PrintWriter(OutputStream o,boolean af){

this(new BufferedWriter(

new OutputStreamWriter(o)), af);

}

Writer arbeiten entsprechend den Reader-Klassen, nur in umgekehrter Richtung.

PrintWriter unterstützen die formatierte Ausgabe von Daten durch die Methoden print und println, die alle Standarddatentypen als Parameter nehmen.

Writer

OutputStreamW PipedW CharArrayW StringW

BufferedW FilterW PrintW

FileW

Beispiel: (Reader-/Writer-Klassen)

public class DateiZugriff { public static

String lesen( String dateiname )

throws FileNotFoundException, IOException {

BufferedReader in = new BufferedReader(

new FileReader( dateiname ) );

String line, inputstr = "";

line = in.readLine();

while( line != null ){

inputstr = inputstr.concat( line+"\n");

line = in.readLine();

}

in.close();

return inputstr;

}

public static

void schreiben(String dateiname,String s) throws IOException {

PrintWriter out;

out = new PrintWriter(

new FileWriter( dateiname ) );

out.print( s );

out.close();

} }

(4)

08.02.2007 © A. Poetzsch-Heffter, TU Kaiserslautern 703

public class DateiZugriffTest {

public static void main( String[] argf ){

String s;

try {

s = DateiZugriff.lesen( argf[0] );

} catch( FileNotFoundException e ){

System.out.println(

"Can't open "+ argf[0] );

return;

} catch( IOException e ){

System.out.println(

"IOException reading "+ argf[0] );

return;

} try {

DateiZugriff.schreiben("ausgabeDatei",s);

} catch( IOException e ){

System.out.println(

"Can't open "+ argf[0] );

} } }

08.02.2007 © A. Poetzsch-Heffter, TU Kaiserslautern 704

Input-/Outputstream-Klassen:

InputStream

FileIS PipedIS ByteArrayIS ObjectIS

DataInput

ObjectInput

SequenceIS FilterIS StringBufferIS

BufferedIS CheckedIS DigestIS DataIS

RandomAccess- File

DataOutput

ObjectOutput InflaterIs

LineNumberIS PushbackIS

ZipIS GZIPIS

OutputStream

FileOS PipedOS ByteArrayOS ObjectOS

FilterOS

BufferedOS CheckedOS DigestOS DataOS

InflaterOS

ZipOS GZIPOS

PrintStream

Objektströme Bemerkung:

Die Unterscheidung in Reader/Writer einerseits und Input-/Output-Ströme andererseits wäre überflüssig, wenn Java parametrische Klassen- deklarationen unterstützen würde, bei denen die Typparameter durch elementare Datentypen instanziert werden können.

Das Lesen und Schreiben von den Werten der elementaren Datentypen ist relativ einfach. Sie besitzen eindeutige Repräsentationen.

Die Ein- und Ausgabe von Objekten ist komplexer:

- Der Zustand reicht zur Repräsentation eines Objektes nicht aus.

- Objektreferenzen besitzen nur innerhalb des aktuellen Prozesses eine Gültigkeit.

- Bei Objekten ist häufig ihre Rolle im Objekt- geflecht von entscheidender Bedeutung.

Andererseits ist Ein- und Ausgabe von Objekten wichtig, um

- Objekte zwischen Prozessen auszutauschen;

- Objekte für nachfolgende Programmläufe zu speichern, d.h. persistent zu machen.

Beispiel: (Objekte: Wie ausgeben?)

LinkedList ll = new LinkedList();

StringBuffer s = new StringBuffer("Sand");

ll.add("Sich ");

ll.add("mit ");

ll.add(s);

ll.add("alen ");

ll.add("im ");

ll.add(s);

ll.add(" aalen");

Was bedeutet es, das von ll referenzierte Objekt auszugeben(?):

- nur das LinkedList-Objekt ausgeben;

- das LinkedList-Objekt und die zugehörigen Entry- Objekte ausgeben;

- das LinkedList-Objekt, die zugehörigen Entry-Objekte sowie die String-Objekte und das StringBuffer-Objekt ausgeben.

(5)

08.02.2007 © A. Poetzsch-Heffter, TU Kaiserslautern 707

Um Objekte in ihrem Zusammenwirken mit anderen Objekten wieder einlesen zu können, müssen sie gemeinsam mit allen erreichbaren Objekten ausgegeben werden.

Dabei bekommen sie eine Kennung, die relativ zu den anderen Objekten des Geflechts eindeutig ist.

Wg. möglicher Zyklen ist die Implementierung der Ausgabe und des Einlesens von Geflechten nicht einfach. Darum gibt es dafür eine Unterstützung in der Bibliothek.

Beispiel: (Ausgabe von Objektgeflechten)

Sei die Variable llwie in obigem Beispiel:

OutputStream os =

new FileOutputStream("speicherndeDatei");

ObjectOutputStream oos =

new ObjectOutputStream( os );

oos.writeObject( ll );

Der Methodenaufruf in der letzten Zeile führt zur Ausgabe aller vonllaus erreichbaren Objekte in die Datei mit Namen „speicherndeDatei“.

08.02.2007 © A. Poetzsch-Heffter, TU Kaiserslautern 708

Das Einlesen von Objekten und den mit ihnen abgelegten erreichbaren Objekten birgt eine weitere Schwierigkeit:

Beim Einlesen müssen Objekte erzeugt werden.

Dafür müssen alle Klassen der einzulesenden Objekte und geeignete Konstruktoren zur Verfügung stehen. (Zum Auffinden benutzt Java die Mechanismen der Reflexion.)

Beispiel: (Einlesen von Objektgeflechten)

Der Methodenaufruf ois.readObject() liest ein Objektgeflecht aus der Datei mit Namen

„speicherndeDatei“ ein.

LinkedList inll;

InputStream is =

new FileInputStream("speicherndeDatei");

ObjectInputStream ois =

new ObjectInputStream( is );

try {

inll = (LinkedList)ois.readObject();

} catch( ClassNotFoundException exc ) {

System.out.println("Klasse zu Objekt fehlt");

}

Zur Beachtung:

• Gibt man ein Objekt mit den erreichbaren Objekten aus und liest es wieder ein, entsteht eine Kopie.

• Referenziert man von mehreren Variablen Teile des gleichen Geflechts, kommt es beim Einlesen ggf.

zu mehreren Kopien eines Objekts des ursprüng- lichen Geflechts.

Beispiel: (Ausgabe u. Einlesen von Objekten)

Vor Ausgabe: Nach Einlesen:

Ausgabe und Einlesen der von undreferenzierten Objekte und Geflechte:

o1:S

a: b: c: a: b: c:

o2:V o11:S o21:V

o3:T

204 204

o4:T o13:T o14:T o4:T o22:T

o5:U true

o15:U true

o5:U true

o23:U true

Begriffsklärung: (Serialisieren)

Serialisieren bedeutet alle von einem Objekt aus erreichbaren Objekte der Reihe nach in kodierter Form in einen Strom zu schreiben.

Deserialisieren bezeichnet den umgekehrten Prozess.

Bemerkung:

• Serialisieren hat zwei zentrale Anwendungen:

- Persistenz von Objekten zu unterstützen;

- Parameterübergabe bei der verteilten objekt- orientierten Programmen zu realisieren.

• Der Serialisierungsmechanismus muss im Allg.

Zugriff auf private Daten haben und adaptierbar sein.

• In Java wird die Serialisierbarkeit der Objekte einer Klasse K dadurch ausgedrückt, dass K die Schnittstelle Serializable implementiert.

Referenzen

ÄHNLICHE DOKUMENTE

Implementieren Sie eine Funktion, die ein Dokument unter Verwendung einer Textvorlage in einen formatierten Text umwandelt:.. format :: Document->

Die Zeilen werden in Worte getrennt, und danach die Zeilen in einem Absatz wieder zusammengefasst, so dass der Text jetzt durch eine Folge von Abs¨ atzen besteht, die aus Folgen

….die Menge an Elektrizitätsteilchen, die pro Sekunde eine Stelle des Leiters durchfließt. ….die Menge an Elektrizitätsteilchen, die im Stromkreis

Die Standardabweichung der Hysteresen in Abb. 5.31 ist meist oben und unten ähnlich. Zu beobachten sind auch einige Hysteresen, bei denen sich sogar die Form der oberen und

Reader Abstrakte Klasse für textuelle Eingabeströme Writer Abstrakte Klasse für textuelle Ausgabeströme InputStream Abstrakte Klasse für binäre Eingabeströme OutputStream

Reader Abstrakte Klasse für textuelle Eingabeströme Writer Abstrakte Klasse für textuelle Ausgabeströme InputStream Abstrakte Klasse für binäre Eingabeströme OutputStream

Den dafür notwendigen Traubenzucker erzeugen die Pflanzen im Gegensatz zu Tieren aber selbst und zwar durch die Photosynthese, die nur in den grünen Pflanzenteilen (welche

An jedem Verzweigungspunkt (Knoten) in einer Schaltung muss ebensoviel Ladung zufließen wie