12.7 Die Klassen um Reader
Die Basisklasse aller Unicode-Eingaben ist die abstrakte Klasse Reader, genauso wie InputStream die Basisklasse für Byte-orientierte Streams ist. Daraus leiten sich weitere Klassen ab, die sequenziellen Zugriff auf Daten erlauben. Wie bei den Writer-Objekten auch, beziehen sich die Unterklassen auf bestimmte Eingabegeräte mit bestimmtem Verhalten.
Die folgende Tabelle gibt einen Überblick über die Klassen und einen Vorgeschmack darauf, was in den folgenden Unterkapiteln beschrieben wird.
Klasse
|
Bedeutung
|
InputStreamReader
|
Abstrakte Basisklasse für alle Reader, die einen Byte-Stream in einen Zeichen-Stream umwandeln
|
FilterReader
|
Abstrakte Basisklasse für Eingabefilter
|
PushbackReader
|
Eingabefilter, der Zeichen zurückgeben kann
|
BufferedReader
|
Reader, der Zeilen puffert
|
StringReader
|
Reader, der aus einem String liest
|
CharArrayReader
|
Reader, der Zeichen-Arrays liest
|
PipedReader
|
Reader, der aus einem PipedWriter liest
|
Tabelle 12.5 Von Reader direkt abgeleitete Klassen
Mit dem FileReader lässt sich aus Dateien lesen. Die Klasse FileReader geht wie ein FileWriter nicht direkt aus der Klasse Reader hervor, sondern ein InputStreamReader sitzt hier noch dazwischen. Auch die Klasse LineNumberReader geht nicht direkt aus dem Reader hervor. Es ist eine Ableitung aus BufferedReader mit der Fähigkeit, Zeilen zu zählen.
12.7.1 Die abstrakte Basisklasse Reader
Die abstrakte Klasse Reader dient zum Lesen von Zeichen aus einem Eingabestrom. Die einzigen Methoden, die Unterklassen implementieren müssen, sind read(char[],int,int) und close(). Dies entspricht dem Vorgehen bei den Writer-Klassen, die auch nur close() und write(char[],int,int) implementieren müssen. Eine abstrakte flush()-Methode, wie sie Writer besitzt, kann Reader nicht haben. Es bleiben demnach für die Reader-Klasse zwei abstrakte Methoden übrig. Die Unterklassen implementieren jedoch auch andere Methoden aus Geschwindigkeitsgründen neu.
abstract class java.io.Reader
|
|
protected Reader()
Erzeugt einen neuen Reader, der sich mit sich selbst synchronisiert. |
|
protected Reader( Object lock )
Erzeugt einen neuen Reader, der mit dem Objekt lock synchronisiert ist. |
|
abstract int read( char cbuf[], int off, int len ) throws IOException
Liest len Zeichen in den Puffer cbuf ab der Stelle off. Wenn len Zeichen nicht vorhanden sind, wartet der Reader. Gibt die Anzahl gelesener Zeichen zurück oder -1, wenn das Ende des Stroms erreicht wurde. |
|
int read() throws IOException
Die parameterlose Methode liest das nächste Zeichen aus dem Eingabestrom. Die Methode wartet, wenn kein Zeichen im Strom bereitliegt. Der Rückgabewert ist ein int im Bereich 0 bis 65636 (0x0000-0xffff). Warum dann der Rückgabewert aber int und nicht char ist, kann leicht damit erklärt werden, dass die Methode den Rückgabewert -1 (0xffffffff) kodieren muss, falls keine Daten anliegen. |
|
int read( char cbuf[] ) throws IOException
Liest Zeichen aus dem Strom und schreibt sie in ein Feld. Die Methode wartet, bis Eingaben anliegen. Der Rückgabewert ist die Anzahl der gelesenen Zeichen oder -1, wenn das Ende des Datenstroms erreicht wurde. |
|
abstract void close() throws IOException
Schließt den Strom. Folgt anschließend noch ein Aufruf von read(), ready(), mark() oder reset(), lösen diese eine IOException aus. Ein doppelt geschlossener Stream hat keinen weiteren Effekt. |
Neben diesen notwendigen Methoden, die bei der Klasse Reader gegeben sind, kommen noch weitere interessante Funktionen hinzu, die den Status abfragen und Positionen setzen lassen. Die Methode ready() liefert als Rückgabe true, wenn ein read() ohne Blockierung der Eingabe möglich ist. Die Implementierung von Reader gibt immer false zurück. Mit der Methode mark() lässt sich eine bestimmte Position innerhalb des Eingabestroms markieren. Die Methode sichert dabei die Position. Mit beliebigen reset()-Aufrufen lässt sich diese konkrete Stelle zu einem späteren Zeitpunkt wieder anspringen. mark() besitzt einen Ganzzahl-Parameter, der angibt, wie viele Zeichen gelesen werden dürfen, bevor die Markierung nicht mehr gültig ist. Die Zahl ist wichtig, da sie die interne Größe des Puffers bezeichnet, der für den Strom angelegt werden muss. Nicht jeder Datenstrom unterstützt dieses Hin- und Herspringen. Die Klasse StringReader unterstützt etwa die Markierung einer Position, die Klasse FileReader dagegen nicht. Daher sollte vorher mit markSupported() überprüft werden, ob das Markieren auch unterstützt wird. Wenn der Datenstrom es nicht unterstützt und wir diese Warnung ignorieren, werden wir eine IOException bekommen. Denn Reader implementiert mark() und read() ganz einfach und muss von uns im Bedarfsfall überschrieben werden.
public void mark(int readAheadLimit) throws IOException {
throw new IOException("mark() not supported");
}
public void reset() throws IOException {
throw new IOException("reset() not supported");
}
Daher gibt markSupported() auch in der Reader-Klasse false zurück.
|
long skip( long n ) throws IOException
Überspringt n Zeichen. Blockt, bis Zeichen vorhanden sind. Gibt die Anzahl der wirklich übersprungenen Zeichen zurück. |
|
public boolean ready() throws IOException
true, wenn aus dem Stream direkt gelesen werden kann. Das heißt allerdings nicht, dass false immer Blocken bedeutet. |
|
boolean markSupported()
Der Stream unterstützt die mark()-Operation. |
|
void mark( int readAheadLimit ) throws IOException
Markiert eine Position im Stream. Der Parameter gibt an, nach wie vielen Zeichen die Markierung ungültig wird, mit anderen Worten, er gibt die Puffergröße an. |
|
void reset() throws IOException
Falls eine Markierung existiert, setzt der Stream an der Markierung an. Wurde die Position vorher nicht gesetzt, dann wird eine IOException mit dem String »Stream not marked« geworfen. |
12.7.2 Automatische Konvertierungen mit dem InputStreamReader
Unsere Basisklasse für alle Reader, die eine Konvertierung zwischen Byte- und Zeichen-Streams vornehmen, ist InputStreamReader. Die Klasse ist nicht abstrakt und hat demnach auch keine abstrakten Methoden. Sie arbeitet wie ein OutputStreamWriter und konvertiert die Daten mit Hilfe eines sun.nio.cs.StreamDecoders. Da wir die Arbeitsweise schon an anderer Stelle beschrieben haben, verzichten wir nun darauf.
class java.io.InputStreamReader
extends Reader
|
|
InputStreamReader( InputStream in )
Erzeugt einen InputStreamReader mit der Standardkodierung. |
|
InputStreamReader(InputStream in, String enc) throws UnsupportedEncodingException
Erzeugt einen InputStreamReader, der die angegebene Zeichenkodierung anwendet. |
|
String getEncoding()
Liefert einen String mit dem Namen der Kodierung zurück. Der Name ist kanonisch und kann sich daher von dem String, der im Konstruktor übergeben wurde, unterscheiden. |
|
int read() throws IOException
Liest ein einzelnes Zeichen oder gibt -1 zurück, falls der Stream am Ende ist. |
|
int read( char cbuf[], int off, int len ) throws IOException
Liest einen Teil eines Felds. |
|
boolean ready() throws IOException
Kann vom Stream gelesen werden. Ein InputStreamReader ist bereit, wenn der Eingabepuffer nicht leer ist oder Bytes des darunter befindlichen InputStreams anliegen. |
Wie wir an dieser Stelle bemerken, unterstützt ein reiner InputStream kein mark() und reset(). Da FileReader die einzige Klasse in der Java-Bibliothek ist, die einen InputStreamReader erweitert, und diese Klasse ebenfalls kein mark() beziehungsweise reset() unterstützt, lässt sich sagen, dass kein InputStreamReader der Standardbibliothek Positionsmarkierungen erlaubt.
Vergleich Reader und InputStream
Ein InputStreamReader eignet sich gut für die Umwandlung von äußeren Bytequellen. Wir erinnern uns, dass Java 16-Bit-Unicode-Zeichen verwendet, aber viele Computersysteme nur mit 8-Bit-ASCII-Zeichen arbeiten. Wenn wir also ein einzelnes Zeichen lesen, muss die passende Konvertierung in das richtige Zeichenformat gesichert sein. Der einfachste Weg ist, ein Zeichen zu lesen und es in ein char - allerdings ohne Konvertierung - zu casten, beispielsweise wie folgt:
InputStream fis = new FileInputStream( "file.txt" );
DataInputStream dis = new DataInputStream( fis );
char c = (char) dis.readByte();
Da hier keine Konvertierung durchgeführt wird, ist dieser Weg nicht gut. Empfehlenswert ist die Verwendung eines InputStreamReader, der die acht Bit in ein Unicode-Zeichen portiert.
InputStream fis = new FileInputStream( "file.txt" );
InputStreamReader isr = new InputStreamReader( fis );
char c = (char) isr.read();
Im nächsten Punkt beschreiben wir die Klasse FileReader, die direkt die Datei öffnet und den FileInputStream für uns anlegt.
12.7.3 Dateien lesen mit der Klasse FileReader
Die Klasse FileReader geht direkt aus einem InputStreamReader hervor. Von der Klasse Writer ist bekannt, dass Konstruktoren hinzugefügt werden, damit die Datei geöffnet werden kann, so auch hier.
class java.io.FileReader
extends InputStreamReader
|
|
public FileReader( String fileName ) throws FileNotFoundException
Öffnet die Datei über einen Dateinamen zum Lesen. Falls sie nicht vorhanden ist, löst der Konstruktor eine FileNotFoundException aus. |
|
public FileReader( File file ) throws FileNotFoundException
Öffnet die Datei zum Lesen über ein File-Objekt. Falls sie nicht vorhanden ist, löst der Konstruktor eine FileNotFoundException aus. |
|
public FileReader( FileDescriptor fd )
Nutzt die schon vorhandene offene Datei über ein FileDescriptor-Objekt. |
Nachfolgend zeigen wir die Anwendung der FileReader-Klasse, die ihren eigenen Quellcode auf den Bildschirm ausgibt. Die Datei muss im korrekten Pfad sein.
Listing 12.19 FileReaderDemo.java
import java.io.*;
public class FileReaderDemo
{
public static void main( String args[] )
{
try
{
FileReader f = new FileReader( "FileReaderDemo.java" );
for ( int c; ( c = f.read() ) != -1; )
System.out.print( (char) c );
f.close();
}
catch ( IOException e ) {
System.out.println( "Fehler beim Lesen der Datei" );
}
}
}
12.7.4 StringReader und CharArrayReader
Die Klassen StringWriter und CharArrayWriter haben die entsprechenden Lese-Klassen mit den Namen StringReader und CharArrayReader. Beide erlauben das Lesen von Zeichen aus einem String beziehungsweise aus einem Zeichenfeld. Sie leiten sich beide direkt aus Writer ab.
Listing 12.20 StringReaderDemo.java
import java.io.*;
class StringReaderDemo
{
public static void main( String args[] ) throws IOException
{
String s = "Hölle Hölle Hölle";
StringReader sr = new StringReader( s );
char H = (char) sr.read();
char ö = (char) sr.read();
for ( int c; (c = sr.read()) != -1; )
System.out.print( (char) c );
sr.close();
}
}
Hier klicken, um das Bild zu Vergrößern
class java.io.StringReader
extends Reader
|
|
StringReader( String s )
Erzeugt einen neuen StringReader, der die Zeichen aus dem String s liest. |
class java.io.CharArrayReader
extends Reader
|
|
CharArrayReader( char buf[] )
Erzeugt einen CharArrayReader vom angegebenen Feld. |
|
CharArrayReader( char buf[], int offset, int length )
Erzeugt einen CharArrayReader vom angegebenen Feld der Länge length und der angegebenen Verschiebung. |
Die Klassen StringReader und CharArrayReader überschreiben die Funktionen close(), mark(int), markSupported(), read(), read(char[] cbuf, int off, int len), ready(), reset() und skip(long). Sie unterstützen skip() und mark() beziehungsweise reset().
Tipp Das Zeichenfeld, das CharArrayReader erhält, wird intern nicht kopiert, sondern referenziert. Das heißt, dass nachträgliche Änderungen am Feld die aus dem Stream gelesenen Zeichen beeinflussen.
|
|