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.
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.
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.
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).
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.
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: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.
- 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.