10.4.1 | Abgeleitete Streams
|
Die abgeleiteten Streams stellen in Java einen großen Teil der
Funktionalität bereit. Unter diese Gruppe fallen alle Streams, zu
deren Erzeugung ein Exemplar eines anderen Streams benötigt wird.
Sie erweitern einen Stream um weitere Funktionen oder filtern die
Daten, die über diesen Stream transportiert werden, auf irgendeine Weise.
Zu letzterer Gruppe zählen im JDK 1.1 alle von FilterReader bzw.
FilterWriter abgeleiteten Klassen.
Im JDK 1.0 waren alle Streams dieser Gruppen von
FilterInputStream bzw. FilterOutputStream
abgeleitet.
Aus einem abgeleiteten Stream
kann nun wiederum ein weiterer abgeleiteter Stream erzeugt werden, der weitergehende
Funktionalität besitzt. So ist es möglich, mehrere
Streams hintereinander zu koppeln.
Jeder Stream verarbeitet die Daten nach bestimmten
Kriterien und gibt sie anschließend an den nächsten Stream
weiter. Dadurch kann man die Funktionalität von Streams inkrementell
erweitern, ohne neue Klassen abzuleiten. Beliebige
Reihenschaltungen von Streams sind somit denkbar.
Print-Streams
Die Klasse PrintWriter stellt im JDK 1.1 das Gegenstück zu PrintStream
im JDK 1.0 dar. Diese Klasse ermöglicht die Ausgabe einer String-Repräsentation
eines beliebigen Objektes.
Hierfür wird für jeden Datentyp eine print() bzw. println()-Methode
zur Verfügung gestellt. In folgendem Beispiel wird aus der Standardausgabe, die
per Voreinstellung in Exemplar von PrintStream ist, ein
PrintWriter erzeugt:
PrintWriter out = new PrintWriter(System.out);
Dies
ist die einfachste Art, einen PrintWriter zu erzeugen.
Über die print() und println()-Methoden können nun beliebige
Datentypen in String-Darstellung auf der Standardausgabe ausgegeben werden.
Im Unterschied zur Ausgabe mit PrintStream wird nun sogar eine
Konvertierung der Strings in den jeweiligen Zeichensatz der Plattform
vorgenommen.
Der Leser wird sich wundern, dass in diesem Fall keine explizite Umwandlung der
Stream-Klasse über die Adapterklasse erfolgt.
Diese Umwandlung wird einfach intern im Konstruktor der Klasse
PrintWriter vorgenommen.
Alternativ dazu kann die Umwandlung aber auch explizit vom Programmierer vorgenommen
werden:
PrintWriter out =
new PrintWriter(new OutputStreamWriter(System.out));
Zu diesem Zweck wird ein anderer Konstruktor verwendet, der einen
Writer als Parameter übergeben bekommt.
Intern werden in der Klasse PrintWriter die über print() und
println() gemachten Ausgaben gepuffert. Dadurch ist nicht unbedingt direkt
nach Aufruf der Ausgabemethode auch die Ausgabe auf dem Bildschirm zu sehen.
Beim Aufruf von print() ist die Ausgabe immer nur dann sofort zu sehen, wenn
sich genügend Daten im internen Puffer angesammelt haben.
Ist der Puffer nämlich voll, so werden alle Daten, die sich im Puffer befinden, ausgegeben.
Ob die
Ausgabe bei Aufruf der Methode println() sofort erfolgt,
hängt von dem Wert des internen Datenelements autoflush ab.
autoflush sollte den Wert true haben, wenn die Ausgabe sofort nach
Aufruf der println()-Methode erfolgen soll.
Hat autoflush den Wert false, so erfolgt die Ausgabe nur bei
vollem Puffer.
Der Wert von autoflush kann nur mit dem Konstruktor eingestellt werden.
Hierzu definiert PrintWriter zwei weitere Konstruktoren, die neben
der Stream- bzw. Writer-Klasse noch einen booleschen Wert zur Initialisierung des Datenelements
autoflush als Parameter besitzen.
In folgendem Beispiel wird der Wert von autoflush auf true gesetzt:
PrintWriter out = new PrintWriter(System.out, true);
Nachdem einmal ein Exemplar von PrintWriter erzeugt ist, kann der
Wert von autoflush nicht mehr geändert werden.
Buffered-Streams
Die Klasse BufferedReader ist wohl
die Klasse, mit der der Programmierer bei der textorientierten Eingabe am
häufigsten in Kontakt kommt. Sie ist deshalb so wichtig, da sie
über eine Methode verfügt, die zeilenweises Einlesen von Strings
ermöglicht, was z. B. beim Einlesen von Text von der Standardeingabe
von Bedeutung ist.
BufferedReader und BufferedWriter steigern ihre Effizienz
dadurch, dass sie die geschriebenen bzw. die gelesenen Zeichen
zwischenpuffern. Der Lese- bzw. Schreibvorgang wird immer
blockweise durchgeführt. Da bei der Character-orientierten Ein- und Ausgabe
immer eine Umwandlung zwischen Byte und Character vorgenommen werden muss, ist
bei diesen Streams nicht für jedes gelesene bzw. geschriebene Byte eine solche
Umwandlung notwendig, sondern immer nur dann, wenn der Puffer voll ist.
Ein Exemplar von BufferedWriter wird auch
intern von PrintWriter für die Pufferung der Ausgabe verwendet.
Eine Effizienzsteigerung kann auch immer dann verzeichnet werden, wenn
das Ein- bzw. Ausgabemedium, von dem die Daten gelesen werden bzw. auf das
die Daten geschrieben werden, relativ langsam ist. Das ist z. B. beim Umgang
mit Dateien der Fall. Größere
Datenmengen am Stück in eine Datei zu schreiben ist
sehr viel effizienter als jedes Byte einzeln. Die in diesem Abschnitt
beschriebenen Streams sind allerdings nur für die Ein- und Ausgabe von Text
geeignet.
Zur Veranschaulichung wird nun das im Abschnitt über Standard-Streams geschilderte
Beispiel mit BufferedReader und BufferedWriter reimplementiert:
import java.io.*;
import java.io.BufferedReader;
public class BufferedReaderDemo {
public static void main(String args[]) {
BufferedReader in =
new BufferedReader(new InputStreamReader(System.in));
BufferedWriter out =
new BufferedWriter(new OutputStreamWriter(System.out));
String text;
try {
do {
text = in.readLine();
out.write(text, 0, text.length());
out.newLine();
out.flush();
} while (! text.equals("exit"));
text = "Thank you very much for using this program";
out.write(text, 0, text.length());
out.newLine();
out.flush();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
Die entsprechenden auf Byte-Basis operierenden Klassen sind
BufferedInputStream und BufferedOutputStream.
Data-Streams
Mit
den Klassen DataInputStream und DataOutputStream
ist es möglich, Standarddatentypen
zu lesen und zu schreiben.
Somit ist es nicht mehr nötig, Datentypen in ein
entsprechendes Format zu konvertieren, wie es etwa bei
dem Beispiel zur Standardein- und -ausgabe der Fall war.
Die Klasse DataInputStream war insbesondere im JDK deshalb wichtig, weil sie es ermöglichte, Text zeilenweise einzulesen, und automatisch eine Konvertierung in einen String vornahm. Die Konvertierung der eingelesenen Bytes in einen String berücksichtigte jedoch nicht unterschiedliche Zeichensätze auf verschiedenen Plattformen.
Diese Aufgabe wird deshalb im JDK 1.1 von der Klasse BufferedReader übernommen.
Das Lesen bzw. Schreiben von beliebigen Datentypen kann z. B. für
das
Speichern von Informationen in Dateien wichtig sein.
Durch die Methoden des DataOutputStreams ist es beispielsweise möglich, int-Werte in Dateien abzulegen. Solch ein int-Wert setzt sich
aus vier Bytes zusammen, die bei der Verwendung eines anderen Streams nicht so einfach
binär gespeichert werden könnten. Analog dazu müsste der Programmierer beim Auslesen eines
solchen int-Wertes aus einer Datei auf die ordnungsgemäße Rekonstruktion
achten. Solche Aufgaben können direkt mit den hier vorgestellten
Data-Streams gelöst werden.
In folgendem Beispiel wird eine beliebige Anzahl Zahlen von der
Standard-Eingabe gelesen, in einer Datei gespeichert und anschließend wieder
auf der Standardausgabe ausgegeben:
import java.io.*;
public class DataStreamDemo {
public static void main(String args[]){
int number = 1; // Eingabepuffer
String text;
// Umwandlung der Standardausgabe in einen Writer
PrintWriter stdout = new PrintWriter(System.out, true);
try {
DataOutputStream dataOutput =
new DataOutputStream(
new FileOutputStream("numbers.dat"));
BufferedReader in =
new BufferedReader(new InputStreamReader(System.in));
do {
try {
// Einlesen einer Zahl
number = Integer.parseInt(in.readLine());
// Ausgabe der eingelesenen Zahl
dataOutput.writeInt(number);
}
// Abfangen von ungültigen Eingaben
catch(NumberFormatException e) {
stdout.println("keine gültige Zahl !");
}
// Abfangen von anderen Fehlern
catch (IOException e) {
e.printStackTrace();
}
}
while(number != 0);
dataOutput.close();
DataInputStream dataInput =
new DataInputStream(
new FileInputStream("numbers.dat"));
stdout.println("Input-Numbers:");
try {
while(true)
stdout.println(dataInput.readInt());
}
// Abbruch des Einlesevorgangs bei Dateiende
catch(EOFException e) {}
catch(IOException e) {
e.printStackTrace();
}
dataInput.close();
}
catch(IOException ex) {
ex.printStackTrace();
}
text ="Thank you very much for using this program";
stdout.println(text);
}
}
Damit eine ordnungsgenäße Zeichenumwandlung
bei der Ausgabe auf der Standardausgabe erfolgt, wird zunächst
der Standardausgabe-Stream in einen PrintWriter umgewandelt:
PrintWriter stdout = new PrintWriter(System.out, true);
autoflush wird hierbei jeweils der Wert true zugewiesen, damit die
Ausgabe auch sofort nach Aufruf von println() erscheint und nicht gepuffert wird.
Anschließend wird der DataOutputStream initialisiert:
DataOutputStream dataOutput =
new DataOutputStream(
new FileOutputStream("numbers.dat"));
Dem Konstruktor wird ein Exemplar des Streams übergeben, der umgewandelt
werden soll. In diesem Fall ist dieser Stream mit der Datei numbers.dat
verbunden, was aber nicht weiter stören soll. Der Umgang mit Dateien wird später detaillierter
besprochen .
Nach der Initialisierung kann man mit
den Methoden von DataOutputStream Daten in die Datei schreiben.
Die Ausgabe in die Datei könnte aber auch direkt mit den Methoden von
FileOutputStream erfolgen.
Die Ausgabe an sich wird bei solch einer Umwandlung nicht modifiziert. Was sich ändert,
sind die Zugriffsmethoden, mit denen Daten in den Stream geschrieben werden.
Danach werden so lange Zahlen von der Standardeingabe gelesen, bis der
Benutzer eine 0 eingibt. Die Zahlen werden mit der Methode
readLine() von BufferedReader als String eingelesen
und jeweils in einen int-Wert konvertiert. Dieser int-Wert wird dann über die
Methode writeInt() der Klasse DataOutputStream in die Datei geschrieben:
dataOutput.writeInt(number);
Gibt der Benutzer keine Zahl, sondern etwas anderes ein, dann wird über ein
selektives catch eine Fehlermeldung ausgegeben:
// Abfangen von ungültigen Eingaben
catch(NumberFormatException e) {
stdout.println("keine gültige Zahl !");
}
// Abfangen von anderen Fehlern
catch (IOException e) {
e.printStackTrace();
}
Integer.parseInt(String) löst eine NumberFormatException aus,
falls der String-Parameter keine gültige Zahl enthält.
Bei Auftreten einer anderen Exception tritt die zweite
catch-Anweisung in Kraft, in der eine Standardfehlermeldung ausgegeben wird.
Nach Eingabe einer 0 durch den Benutzer wird die Eingabe abgebrochen und
der DataOutputStream geschlossen:
dataOutput.close();
Nun erfolgt das Auslesen der Datei mit Hilfe eines DataInputStreams.
Die Initialisierung von DataInputStream erfolgt analog zu
DataOutputStream:
DataInputStream dataInput =
new DataInputStream(
new FileInputStream("numbers.dat"));
In diesem Fall wird dem Konstruktor ein Exemplar von InputStream
übergeben, hier ein FileInputStream.
Danach können mit den Methoden von DataInputStream Daten aus der
Datei eingelesen werden.
Im Beispielprogramm werden so lange int-Werte aus der Datei gelesen und
auf der Standardausgabe ausgegeben, bis
das Dateiende erreicht ist:
try {
while(true)
stdout.println(dataInput.readInt());
}
// Abbruch des Einlesevorgangs bei Dateiende
catch(EOFException e) {}
catch(IOException e) {
e.printStackTrace();
}
Der Auslesevorgang erfolgt mit der
Methode readInt() der Klasse
DataInputStream. readInt() liest genau einen
int-Wert aus dem Stream und gibt ihn als Ergebnis zurück.
Die Abbruchbedingung ist auch in diesem Fall mit einer selektiven
catch-Anweisung implementiert.
readInt() löst eine EOFException aus, wenn man versucht über das
Dateiende hinauszulesen. In diesem Fall muss der
Einlesevorgang beendet und der Stream geschlossen werden.
In diesem Beispiel werden die Data-Streams nur für die Verarbeitung von
int-Werten eingesetzt. Sie können aber auch für alle anderen Standarddatentypen eingesetzt werden. Es muss nur darauf geachtet werden,
dass der Lese- und Schreibvorgang mit den Methoden durchgeführt werden, die
auf demselben Datentyp operieren.
Bei der Verwendung der Methoden readChars() bzw. writeChars() sollte
man beachten, dass sie mit Unicode-Zeichen arbeiten. Bei einer Ausgabe mit
writeChars() bzw. einem Lesevorgang mit readChars()
werden somit immer 2 Bytes pro Zeichen geschrieben
bzw. gelesen. Wenn man mit writeChars() Daten in eine Datei schreibt, kann das
unter Umständen zu Problemen führen, wenn man die Datei in einer
anderen Anwendung weiterverarbeiten möchte, die nicht Unicode lesen kann.
Der Einsatz von DataOutputStream ist nur sinnvoll, wenn wirklich mit
unterschiedlichen Datentypen gearbeitet wird. Zur reinen Textausgabe ist
ein PrintStream bzw. PrintWriter
dem DataOutputStream in jedem Fall vorzuziehen.
Bei der Benutzung des DataInputStreams sollte man beachten, dass
wirklich immer soviele Bytes gelesen werden, wie für einen
Datentyp gebraucht werden: Soll z. B. mit der Methode readInt()
eine Zahl vom Typ int von der Standardeingabe gelesen werden, muss
der Benutzer auch wirklich vier Zeichen eintippen, sonst ist die Eingabe
nicht abgeschlossen. Dazu sollte noch erwähnt werden, dass readInt() eine
Zahl vom Typ int liest. Wird mit dieser Methode eine Zahl von der
Standardeingabe eingelesen, gibt der Benutzer einzelne Zeichen ein.
readInt() konvertiert die vier eingegebenen Zeichen
in den entsprechenden ASCII-Wert und setzt das entsprechende Byte der
int-Zahl auf diesen Wert. Dadurch ergibt sich eine
andere Zahl, als der Benutzer vielleicht erwarten würde.
Indem man sich den Inhalt der Datei numbers.dat anschaut, kann man
leicht überprüfen, dass die Ausgabe von writeInt() wirklich binär ist.
Da ein int-Wert immer vier Bytes belegt, ist die Größe der Datei immer
ein Vielfaches von vier.
Data-Streams
können z. B. sehr gut für die Netzwerkkommunikation in einer
Client-Server-Anwendung eingesetzt werden. Ein Protokoll zur Client-Server-Kommunikation
sollte möglichst einfach gehalten werden, jedoch auch relativ flexibel sein. Dadurch, dass mit
den Data-Streams beliebige Datentypen geschrieben bzw. gelesen werden können, haben Client bzw.
Server bereits eine relativ abstrakte Schnittstelle für den Datenzugriff zur Verfügung.
Eine Typkonvertierung würde somit sowohl auf Client- als auch auf Server-Seite entfallen. Es wäre
denkbar, dass die Nachrichtentypen, die zwischen Client und Server ausgetauscht werden, über
int-Werte gekennzeichnet sind. Die Daten, die mit einer Nachricht verschickt werden,
könnten anschließend durch einen ganz anderen Datentyp dargestellt werden. Die Data-Streams eignen sich
unter anderem sehr gut für die Übertragung von Strings. Ein Socket, der für die
Client-Server-Kommunikation notwendig ist, überträgt Daten prinzipiell binär. D. h. für die
Übertragung von Text ist eine Konvertierung des Textes in Byte-Form notwendig. Beim Lesen muss der
umgekehrte Vorgang durchgeführt werden.
Bei einer Konvertierung in die plattformabhängige Zeichenkodierung könnte es zu Verfälschungen
kommen, falls Client und Server unterschiedliche Zeichenkodierungen verwenden. Deshalb bietet es sich an,
Daten direkt in Unicode zu verschicken. Die Verwendung der Methode writeChars() stellt eine
Möglichkeit dar, dies zu tun.
Ein anderes Problem bei der Netzwerkprogrammierung besteht oft darin, herauszufinden,
wie lang eine Nachricht ist. Besteht eine Nachricht aus mehreren Textzeilen, müssen die
Kommunikationspartner wissen, wieviele Bytes nun zu dieser Nachricht gehören.
Die Methoden
readUTF() und writeUTF() ermöglichen das Schreiben bzw.
Lesen von mehrzeiligen Textdaten auf relativ einfache Weise. readUTF() liefert
genau den Text zurück, der zuvor
mit writeUTF() in einen Stream geschrieben wurde, unabhängig davon, aus wievielen Zeilen er besteht.
Mit writeChars() ist zwar das Schreiben von mehrzeiligem Text möglich, readChar()
ermöglicht jedoch nur zeichenweises Lesen.
Die Zeichenkodierung spielt bei readUTF() und writeUTF() ebenfalls keine Rolle, da die Daten als
Unicode im UTF-8-Format übertragen werden (Nähere Informationen zu UTF-8 befinden
sich in Abschnitt 2.1.1; die Netzwerkprogrammierung wird in
Kapitel 13 behandelt).
LineNumber-Streams
LineNumberReader ist eine Klasse, die es dem
Benutzer
ermöglicht, auf die Anzahl der gelesenen Zeilen
zuzugreifen.
Um dies zu demonstrieren, folgt nun ein kleines Beispiel.
Der Benutzer gibt zeilenweise Text von der Standardeingabe ein.
Jede eingegebene Zeile wird vom Programm zusammen mit der
Zeilennummer auf der Standardausgabe ausgegeben.
Besteht die Eingabe nur aus dem Wort exit, so wird das Programm
beendet.
Der Quelltext zu diesem Programm ist nicht sehr umfangreich:
public class LineNumberDemo {
public static void main(String args[]) {
String line = ""; // Eingabepuffer
int zeichen;
// Konvertierung der Standard-Streams
LineNumberReader lineInput = new LineNumberReader(
new InputStreamReader(System.in));
PrintWriter stdout=new PrintWriter(System.out, true);
do {
try {
// Einlesen einer Zeile
line = lineInput.readLine();
// Ausgabe der eingelesenen Zeile mit Zeilennr.
stdout.println(lineInput.getLineNumber()+
" "+line);
}
catch(IOException e) {
e.printStackTrace();
}
} while(! line.equals("exit"));
stdout.println(
"Thank you very much for using this program");
}
}
Als erster Schritt wird aus der Standardeingabe über die Adapterklasse
InputStreamReader ein
LineNumberReader erzeugt.
LineNumberReader lineInput =
new LineNumberReader(new InputStreamReader(System.in));
Da die Klasse LineNumberReader bereits von der Klasse
BufferedReader abgeleitet ist, kann man zum Einlesen
von Text auf die Methode readLine() zurückgreifen.
Um nun zusätzlich zum eingelesenen Text die
Zeilennummer auszugeben, wird die Methode
getLineNumber()
der Klasse LineNumberReader benutzt:
stdout.println(lineInput.getLineNumber()+" "+line);
getLineNumber() liefert als Ergebnis die aktuelle Anzahl der gelesenen Zeilen.
Man kann die Zeilenzahl aber nicht nur abfragen.
Mit der Methode setLineNumber(int)
kann man dem Stream explizit eine Zeilenzahl zuweisen.
Es ist möglich, eine weitere Stream-Klasse um einen LineNumberReader
zu schachteln. Hierbei sollte man allerdings darauf achten, dass man dafür keinen
gepufferten Stream benutzt. Man könnte z. B. von BufferedReader eine
eigene Klasse BufferedCounter ableiten,
die die Anzahl von Worten oder Zeichen zählt, die über den Stream übertragen werden.
Diesen Stream
kann man dann mit einem LineNumberReader konfigurieren, um außerdem
die Anzahl an Zeilen zu erfahren:
BufferedCounter b = new BufferedCounter(lineReader);
Im obigen Beispiel referenziert die Variable lineReader ein Exemplar der
Klasse LineNumberReader.
Dadurch kann es aber zu Inkonsistenzen zwischen den von LineNumberReader
und von BufferedCounter gelesenen Zeichen kommen. Da die Klasse BufferedCounter
gepuffert arbeitet, erfolgt der Lesevorgang blockweise. Die Klasse liest zunächst soviele Zeichen
ein, wie der Puffer aufnehmen kann bzw. wie verfügbar sind. Bei einem Leseaufruf werden dann
nur so viele Daten zurückgeliefert, wie angefordert werden. Die restlichen Daten verbleiben im
Puffer und werden beim nächsten Lesevorgang zurückgeliefert. Die Klasse BufferedCounter
erhält in diesem Fall die Daten von der Klasse LineNumberReader. Wird von
BufferedCounter ein Datenblock von LineNumberReader angefordert, so zählt
der LineNumberReader seine Zeilenanzahl um die Anzahl an Zeilen hoch, die im gelesenen
Datenblock
enthalten sind. Da die Daten vom BufferedReader aber nicht unbedingt komplett
an den
Aufrufer zurückgegeben werden, sondern eventuell erst zu einem späteren Zeitpunkt, da sie
zwischengepuffert werden, kann es eine
Inkonsistenz geben zwischen den vom LineNumberReader und BufferedCounter
gelesenen Zeilen. Dadurch liefert getLineNumber() ein für den Benutzer
nicht einsichtiges Ergebnis. Die Zeilen, die den Differenzbetrag zwischen tatsächlichem Wert
und Ergebnis von getLineNumber() enthalten, sind im Puffer des BufferedCounters
zu suchen.
Benötigt man eine solche Funktionalität, wie sie hier von BufferedCounter zur
Verfügung gestellt wird, so sollte man keinen gepufferten Stream verwenden.
Hierzu kann man z. B. von der Klasse FilterReader eine Unterklasse bilden.
Neben LineNumberReader enthält das JDK außerdem die Klasse
LineNumberInputStream. Diese Klasse stellt in diesem Fall nicht
eine zu LineNumberReader ebenbürtige Klasse dar, die auf Byte-Basis arbeitet.
Da es sich beim Einlesen von Zeilen immer um die Verarbeitung von Text handelt, wird man nie bei der Verarbeitung von binären Daten einen LineNumberReader verwenden.
Die Klasse LineNumberInputStream entstammt dem JDK 1.0 und sollte nicht mehr
verwendet werden.
Pushback-Streams
Die Klasse PushbackReader ist ebenfalls von der Klasse
FilterReader abgeleitet. Sie bietet dem
Benutzer die Möglichkeit, Zeichen in den Stream zurückzuschreiben.
Hierzu besitzt sie die Methode unread(), von der es drei Varianten gibt.
Das folgende Beispiel demonstriert, wie man ein Zeichen in einen
Stream zurückschreiben kann:
import java.io.*;
public class PushBackDemo {
public static void main(String args[]) {
PrintWriter stdout=new PrintWriter(System.out,true);
// Anlegen eines PushbackReaders
// aus der Standardeingabe
PushbackReader pb =
new PushbackReader(new InputStreamReader(System.in));
int buf = 0;
try {
// Schreiben eines Zeichens
// in den Standardeingabe-Stream
pb.unread('!');
do {
// Einlesen eines Zeichens
buf = pb.read();
// Ausgabe des eingelesenen Zeichens
stdout.print((char)buf);
}
while(buf != (int)'\n'); // Abbruch bei Newline
}
catch (IOException e) {
e.printStackTrace();
}
}
}
Aus der Standardeingabe wird zunächst über
die Adapterklasse InputStreamReader ein
PushbackReader erzeugt.
PushbackReader pb =
new PushbackReader(new InputStreamReader(System.in));
Anschließend wird sofort ein Zeichen in diesen Stream zurückgeschrieben:
pb.unread('!');
Danach werden so lange Zeichen von
der Standardeingabe eingelesen und sofort wieder ausgegeben, bis ein
Newline-Zeichen folgt. Ist dies geschehen,
wird das Programm beendet. Wie man feststellen kann, ist das zuerst in die
Standardeingabe zurückgeschriebene Zeichen auch in der Ausgabe enthalten.
Mit einem PushbackReader kann man nicht nur ein Zeichen
in einen Stream zurückschreiben, sondern auch mehrere. Zu diesem Zweck wird
ein zweiter Konstruktor definiert:
PushbackReader pb =
new PushbackReader(new InputStreamReader(System.in), 5);
Im obigen Beispiel wird ein PushbackReader erzeugt, in den maximal
fünf Zeichen zurückgeschrieben werden können. Der zweite Parameter gibt in diesem
Fall die Anzahl der zurückgeschriebenen Zeichen an, die vom PushbackReader
verwaltet werden können. Wenn man in diesem Beispiel drei Zeichen in den Stream
zurückschreibt, dann ein Zeichen liest und anschließend wieder vier Zeichen zurückschreibt, ist
die Kapazität des Puffers überschritten, obwohl bei keinem Schreibvorgang die maximale
Anzahl ausgenutzt wurde.
Bei Überschreitung der Kapazität des internen Puffers löst unread() deshalb
IOException aus.
Für das Zurückschreiben von mehreren Zeichen definiert PushbackReader
zwei weitere unread()-Methoden:
- public void unread(char cbuf[])
Schreibt die Zeichen in cbuf in den Stream zurück.
- public void unread(char cbuf[], int off, int len)
Schreibt len Anzahl Zeichen ab der Position off aus dem Array cbuf in
den Stream zurück.
Ein PushbackReader kann man z. B. bei der Implementierung eines
Text-Scanners einsetzen. Bei Text-Scannern ist es oftmals notwendig, dass man
ein Zeichen vorauslesen muss, um genau entscheiden zu können, zu welcher Kategorie
eine Zeichenkette gehört. Da das zuviel gelesene Zeichen aber auch noch für die
nächste zu lesende Zeichenkette gebraucht wird, kann man es wieder in den
Eingabe-Stream zurückschreiben.
PushbackInputStream besitzt dieselbe Funktionalität wie PushbackReader,
arbeitet aber auf Byte-Basis.
Copyright © 2002 dpunkt.Verlag, Heidelberg. Alle Rechte vorbehalten.