Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.
 
Inhaltsverzeichnis
Vorwort
1 Neues in Java 8 und Java 7
2 Fortgeschrittene String-Verarbeitung
3 Threads und nebenläufige Programmierung
4 Datenstrukturen und Algorithmen
5 Raum und Zeit
6 Dateien, Verzeichnisse und Dateizugriffe
7 Datenströme
8 Die eXtensible Markup Language (XML)
9 Dateiformate
10 Grafische Oberflächen mit Swing
11 Grafikprogrammierung
12 JavaFX
13 Netzwerkprogrammierung
14 Verteilte Programmierung mit RMI
15 RESTful und SOAP-Web-Services
16 Technologien für die Infrastruktur
17 Typen, Reflection und Annotationen
18 Dynamische Übersetzung und Skriptsprachen
19 Logging und Monitoring
20 Sicherheitskonzepte
21 Datenbankmanagement mit JDBC
22 Java Native Interface (JNI)
23 Dienstprogramme für die Java-Umgebung
Stichwortverzeichnis

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java SE 8 Standard-Bibliothek von Christian Ullenboom
Das Handbuch für Java-Entwickler
Buch: Java SE 8 Standard-Bibliothek

Java SE 8 Standard-Bibliothek
Pfeil 7 Datenströme
Pfeil 7.1 Stream-Klassen für Bytes und Zeichen
Pfeil 7.1.1 Lesen aus Dateien und Schreiben in Dateien
Pfeil 7.1.2 Byteorientierte Datenströme über Files beziehen
Pfeil 7.1.3 Zeichenorientierte Datenströme über Files beziehen
Pfeil 7.1.4 Funktion von OpenOption bei den Files.newXXX(…)-Methoden
Pfeil 7.1.5 Ressourcen aus dem Klassenpfad und aus JAR‐Archiven laden
Pfeil 7.1.6 Die Schnittstellen Closeable, AutoCloseable und Flushable
Pfeil 7.2 Basisklassen für die Ein-/Ausgabe
Pfeil 7.2.1 Die abstrakten Basisklassen
Pfeil 7.2.2 Übersicht über Ein-/Ausgabeklassen
Pfeil 7.2.3 Die abstrakte Basisklasse OutputStream
Pfeil 7.2.4 Ein Datenschlucker *
Pfeil 7.2.5 Die abstrakte Basisklasse InputStream
Pfeil 7.2.6 Ströme mit SequenceInputStream zusammensetzen *
Pfeil 7.2.7 Die abstrakte Basisklasse Writer
Pfeil 7.2.8 Die Schnittstelle Appendable *
Pfeil 7.2.9 Die abstrakte Basisklasse Reader
Pfeil 7.3 Formatierte Textausgaben
Pfeil 7.3.1 Die Klassen PrintWriter und PrintStream
Pfeil 7.3.2 System.out, System.err und System.in
Pfeil 7.4 Die FileXXX-Stromklassen
Pfeil 7.4.1 Kopieren mit FileOutputStream und FileInputStream
Pfeil 7.4.2 Das FileDescriptor-Objekt *
Pfeil 7.4.3 Mit dem FileWriter Texte in Dateien schreiben
Pfeil 7.4.4 Zeichen mit der Klasse FileReader lesen
Pfeil 7.5 Schreiben und Lesen aus Strings und Byte-Feldern
Pfeil 7.5.1 Mit dem StringWriter ein String-Objekt füllen
Pfeil 7.5.2 CharArrayWriter
Pfeil 7.5.3 StringReader und CharArrayReader
Pfeil 7.5.4 Mit ByteArrayOutputStream in ein Byte-Feld schreiben
Pfeil 7.5.5 Mit ByteArrayInputStream aus einem Byte-Feld lesen
Pfeil 7.6 Datenströme filtern und verketten
Pfeil 7.6.1 Streams als Filter verketten (verschachteln)
Pfeil 7.6.2 Gepufferte Ausgaben mit BufferedWriter und BufferedOutputStream
Pfeil 7.6.3 Gepufferte Eingaben mit BufferedReader/BufferedInputStream
Pfeil 7.6.4 LineNumberReader zählt automatisch Zeilen mit *
Pfeil 7.6.5 Daten mit der Klasse PushbackReader zurücklegen *
Pfeil 7.6.6 DataOutputStream/DataInputStream *
Pfeil 7.6.7 Basisklassen für Filter *
Pfeil 7.6.8 Die Basisklasse FilterWriter *
Pfeil 7.6.9 Ein LowerCaseWriter *
Pfeil 7.6.10 Eingaben mit der Klasse FilterReader filtern *
Pfeil 7.6.11 Anwendungen für FilterReader und FilterWriter *
Pfeil 7.7 Vermittler zwischen Byte-Streams und Unicode-Strömen
Pfeil 7.7.1 Datenkonvertierung durch den OutputStreamWriter
Pfeil 7.7.2 Automatische Konvertierungen mit dem InputStreamReader
Pfeil 7.8 Kommunikation zwischen Threads mit Pipes *
Pfeil 7.8.1 PipedOutputStream und PipedInputStream
Pfeil 7.8.2 PipedWriter und PipedReader
Pfeil 7.9 Prüfsummen
Pfeil 7.9.1 Die Schnittstelle Checksum
Pfeil 7.9.2 Die Klasse CRC32
Pfeil 7.9.3 Die Adler32-Klasse
Pfeil 7.10 Persistente Objekte und Serialisierung
Pfeil 7.10.1 Objekte mit der Standardserialisierung speichern und lesen
Pfeil 7.10.2 Zwei einfache Anwendungen der Serialisierung *
Pfeil 7.10.3 Die Schnittstelle Serializable
Pfeil 7.10.4 Nicht serialisierbare Attribute aussparen
Pfeil 7.10.5 Das Abspeichern selbst in die Hand nehmen
Pfeil 7.10.6 Tiefe Objektkopien *
Pfeil 7.10.7 Versionenverwaltung und die SUID
Pfeil 7.10.8 Wie die ArrayList serialisiert *
Pfeil 7.10.9 Probleme mit der Serialisierung
Pfeil 7.11 Alternative Datenaustauschformate
Pfeil 7.11.1 Serialisieren in XML-Dateien
Pfeil 7.11.2 XML-Serialisierung von JavaBeans mit JavaBeans Persistence *
Pfeil 7.11.3 Die Open-Source-Bibliothek XStream *
Pfeil 7.11.4 Binäre Serialisierung mit Google Protocol Buffers *
Pfeil 7.12 Zum Weiterlesen
 
Zum Seitenanfang

7.6Datenströme filtern und verketten Zur vorigen ÜberschriftZur nächsten Überschrift

So wie im alltäglichen Leben Filter beim Kaffee oder bei Fotoapparaten eine große Rolle spielen, so sind sie auch bei Datenströmen zu finden. Immer dann, wenn Daten von einer Quelle gelesen oder in eine Senke geschrieben werden, können Filter die Daten auf dem Weg verändern. Die Java-Bibliothek sieht eine ganze Reihe von Filtern vor, die sich zwischen die Kommunikation schalten können:

Eingabe

Ausgabe

Anwendung

BufferedInputStream

BufferedOutputStream

Daten puffern

BufferedReader

BufferedWriter

CheckedInputStream

CheckedOutputStream

Checksumme berechnen

DataInputStream

DataOutputStream

primitive Datentypen aus dem Strom holen und in den Strom schreiben

DigestInputStream

DigestOutputStream

Digest (Checksumme) mitberechnen

InflaterInputStream

DeflaterOutputStream

Kompression von Daten

LineNumberInputStream

Mitzählen von Zeilen

LineNumberReader

PushbackInputStream

Daten in den Lesestrom zurücklegen

PushbackReader

CipherInputStream

CipherOutputStream

Daten verschlüsseln und entschlüsseln

Tabelle 7.7Filter zwischen Ein- und Ausgabe

Der CipherOutputStream stammt aus dem Paket javax.crypto, manche Typen sind aus java.util.zip, DigestInputStream/DigestOutputStream sind aus dem Paket java.security, und alle anderen stammen aus java.io.

 
Zum Seitenanfang

7.6.1Streams als Filter verketten (verschachteln) Zur vorigen ÜberschriftZur nächsten Überschrift

Die Funktionalität der bisher vorgestellten Ein-/Ausgabeklassen reicht für den Alltag zwar aus, doch sind Ergänzungen gefordert, die die Fähigkeiten der Klassen erweitern; so zum Beispiel beim Puffern. Da die Programmlogik zur Pufferung mit Daten unabhängig von der Quelle ist, aus der die Daten stammen, findet sich die Pufferung in einer gesonderten Klasse. Java implementiert hier ein bekanntes Muster, das sich Dekorator nennt.

[zB]Beispiel

Das Modell für diese Verkettung ist immer gleich, am Beispiel fiktiver Writer-Klassen:

Writer w1 = new XXXWriter( wasauchimmer );
Writer w2 = new YYYWriter( w1 );
Writer w3 = new ZZZWriter( w2 );

Oder anders geschrieben:

Writer w = new ZZZWriter( new YYYWriter( new XXXWriter( wasauchimmer ) ) );

Da wir das Schließen nicht vergessen dürfen, setzen wir in der Regel die Kette in einen try-mit-Ressourcen-Block:

try ( Writer w1 = new XXXWriter( wasauchimmer );
Writer w2 = new YYYWriter( w1 );
Writer w3 = new ZZZWriter( w2 ) ) { … }

Der try-Block schließt w3 zuerst, dann w2 und w1. Dass in der Regel ein w3.close() ein w2.close() involviert, und w2 dann noch einmal vom try mit Ressourcen geschlossen wird, ist in der Regel in Ordnung, da mehrfaches Schließen nicht falsch ist. Es gibt aber Ressourcen, die nur einmal geschlossen werden können, weil sie sonst Ausnahmen werden – diese lassen sich dann verkettet nicht mit try mit Ressourcen verwalten.

Schauen wir uns die Klassen im Paket java.io genau an, die andere Ströme im Konstruktor entgegennehmen:

  • BufferedWriter, PrintWriter, FilterWriter nehmen Writer.

  • BufferedReader, FilterReader, LineNumberReader, PushbackReader, StreamTokenizer nehmen Reader.

  • BufferedOutputStream, DataOutputStream, FilterOutputStream, ObjectOutputStream,
    OutputStreamWriter, PrintStream, PrintWriter nehmen OutputStream.

  • BufferedInputStream, DataInputStream, FilterInputStream, InputStreamReader,
    ObjectInputStream, PushbackInputStream nehmen InputStream.

Beispiel zum BufferedInputStream mit Datei-Reader und einigen anderen Klassen

Mit Files.newInputStream(…) bekommen wir einen InputStream, der aus Dateien list. Wir wollen jedoch Zugriffe immer puffern, denn Dateizugriffe sind sonst etwas langsam. Anstatt also Byte für Byte aus der Datei zu lesen, soll erst einmal ein Bündel von Daten in einen Puffer gelesen werden und Lesezugriffe dann aus diesem Puffer heraus erfolgen. Der Konstruktor von BufferedInputStream nimmt jeden beliebigen InputStream entgegen, denn der Pufferung ist es egal, ob die Daten aus einer Datei, Datenbank oder vom Netzwerk gelesen werden. Das Prinzip ist also immer, dass der Filter einen anderen Strom annimmt, an den er die Daten weitergibt oder von dem er sie holt. In unserem Fall: Der BufferedInputStream wird also beim ersten Lesen an die Datei gehen und sich den Puffer vollmachen. Jeder Lesezugriff vom Programm geht an den BufferedInputStream und nicht an den Stream von der Datei. Beim BufferedInputStream wollen wir aber nicht aufhören. Wir ummanteln diesen InputStream mit einem DigestInputStream, um beim Einlesen eine Checksumme berechnen zu können; damit lässt sich feststellen, ob die Datei verändert wurde. Danach übergeben wir den DigestInputStream an einen InputStreamReader, der den Byte-Strom in einen Zeichenstrom konvertiert. Diesen Reader übergeben wir Scanner, damit wir zeilenweise die Datei ausgeben können:

Listing 7.16com/tutego/insel/io/writer/ChainedReader.java, main()

try ( InputStream fis = Files.newInputStream( Paths.get( "lyrics.txt" ) );
InputStream bis = new BufferedInputStream( fis );
DigestInputStream dis = new DigestInputStream( bis,
MessageDigest.getInstance( "SHA-256" ) );
Reader isr = new InputStreamReader( dis, StandardCharsets.ISO_8859_1 );
Scanner scanner = new Scanner( isr ) ) {
while ( scanner.hasNextLine() )
System.out.println( scanner.nextLine() );
System.out.printf( "%X", new BigInteger( 1, dis.getMessageDigest().digest() ) );
}
catch ( IOException e ) {
System.err.println( "Konnte Datei nicht einlesen!" );
e.printStackTrace();
}
catch ( NoSuchAlgorithmException e ) {
System.err.println( "SHA-256 ist unbekannt!" );
}
 
Zum Seitenanfang

7.6.2Gepufferte Ausgaben mit BufferedWriter und BufferedOutputStream Zur vorigen ÜberschriftZur nächsten Überschrift

Die Klassen BufferedWriter und BufferedOutputStream haben die Aufgabe, die mittels write(…) in den Ausgabestrom geleiteten Ausgaben zu puffern. Dies ist immer dann nützlich, wenn viele Schreiboperationen gemacht werden, denn das Puffern macht insbesondere Dateioperationen wesentlich schneller, da so mehrere Schreiboperationen zu einer zusammengefasst werden. Um die Funktionalität eines Puffers zu erhalten, besitzen die Klassen einen internen Puffer, in dem die Ausgaben von write(…) zwischengespeichert werden. Standardmäßig fasst der Puffer 8.192 Symbole. Er kann aber über einen parametrisierten Konstruktor auf einen anderen Wert gesetzt werden. Erst wenn der Puffer voll ist oder die Methoden flush() oder close() aufgerufen werden, werden die gepufferten Ausgaben geschrieben. Durch die Verringerung der Anzahl tatsächlicher write(…)-Aufrufe an das externe Gerät erhöht sich die Geschwindigkeit der Anwendung im Allgemeinen deutlich.

BufferedWriter ist ein Writer und dekoriert einen anderen Writer.

Abbildung 7.7BufferedWriter ist ein Writer und dekoriert einen anderen Writer.

Um einen BufferedWriter/BufferedOutputStream anzulegen, gibt es zwei Konstruktoren, denen ein bereits existierender Writer/OutputStream übergeben wird. An diesen Writer/OutputStream wird dann der Filter seinerseits die Ausgaben weiterleiten, insbesondere nach einem Aufruf von flush(), close() oder einem internen Überlauf.

class java.io.BufferedWriter class java.io.BufferedOutputStream
extends Writer extends FilterOutputStream
  • BufferedWriter(Writer out)

  • BufferedOutputStream(OutputStream out)
    Erzeugt einen puffernden Ausgabestrom mit der Puffergröße von 8.192 Symbolen.

  • BufferedWriter(Writer out, int sz)

  • BufferedOutputStream(OutputStream out, int size)
    Erzeugt einen puffernden Ausgabestrom mit einer Puffergröße. Ist sie nicht echt größer 0, gibt es eine IllegalArgumentException.

Alle write(…)- und append(…)-Methoden sind so implementiert, dass die Daten erst im Puffer landen. Wenn der Puffer voll ist – oder flush() aufgerufen wird –, werden sie an den im Konstruktor übergebenen Writer durchgespült.

Zusätzlich bietet die Klasse BufferedWriter die Methode newLine(), die in der Ausgabe eine neue Zeile beginnt. Das Zeichen für den Zeilenwechsel wird aus der Systemeigenschaft line.separator genommen. Da sie intern mit der write(…)-Methode arbeitet, kann sie eine IOException auslösen.

[»]Hinweis

Ein über Files bezogener Reader/Writer ist automatisch gepuffert; das drückt auch schon der Methodenname aus: Files.newBufferedReader(…) und Files.newBufferedWriter(…). Sie noch einmal zu ummanteln ist unnötig.

 
Zum Seitenanfang

7.6.3Gepufferte Eingaben mit BufferedReader/BufferedInputStream Zur vorigen ÜberschriftZur nächsten Überschrift

Die Klassen BufferedReader und BufferedInputStream puffern Eingaben. Die Daten werden also zuerst in einen Zwischenspeicher geladen, was insbesondere bei Dateien zu weniger Zugriffen auf den Datenträger führt und so die Geschwindigkeit der Anwendung erhöht. Neben der Eigenschaft, dass BufferedReader als Filter eingesetzt wird, bietet er noch zwei Extramethoden zum komfortablen Lesen von Eingaben.

Die Klassen BufferedReader und BufferedInputStream besitzen je zwei Konstruktoren. Bei einem lässt sich die Größe des internen Puffers angeben. Die Puffergröße beträgt wie beim BufferedWriter/BufferedOutputStream standardmäßig 8.192 Einträge.

class java.io.BufferedReader class java.io.BufferedInputStream
extends Reader extends FilterInputStream
  • BufferedReader(Reader in)

  • BufferedInputStream(InputStream in)
    Erzeugt einen puffernden Zeichenstrom mit der Puffergröße von 8.192.

  • BufferedReader( Reader in, int sz )

  • BufferedInputStream(InputStream in, int size)
    Erzeugt einen puffernden Zeichenstrom mit der gewünschten Puffergröße.

Da ein BufferedReader Markierungen und Sprünge erlaubt, werden die entsprechenden Methoden von Reader überschrieben.

[»]Hinweis

Insbesondere beim externen Hintergrundspeichern ergibt eine Pufferung Sinn. So sollten zum Beispiel die dateiorientierten Klassen immer gepuffert werden, insbesondere, wenn einzelne Bytes/Zeichen gelesen oder geschrieben werden.

Ohne Pufferung

In der Regel schneller mit Pufferung

new FileInputStream(f)

new BufferedInputStream(new FileInputStream(f))

new FileOutputStream(f)

new BufferedOutputStream(new FileOutputStream(f))

Zeilen lesen mit BufferedReader und readLine()/stream()

Die Klasse BufferedReader stellt die Methode readLine() zur Verfügung, die eine komplette Textzeile liest und als String an den Aufrufer zurückgibt; BufferedOutputStream als byteorientierte Klasse bietet die Methode nicht an. Mit dieser Methode lässt sich jeder Datenstrom komplett Zeile für Zeile ablaufen. Eine weitere Methode ist neu seit Java 8: lines(). Sie liefert keinen String, sondern einen Stream von String-Objekten. Für ein Beispiel siehe auch Abschnitt 7.1.3, »Zeichenorientierte Datenströme über Files beziehen«.

class java.io.BufferedReader
extends Reader
  • String readLine() throws IOException
    Liest eine Zeile bis zum Zeilenende und gibt den String ohne die Endezeichen zurück. Die Rückgabe ist null, wenn der Stream am Ende ist.

  • Stream<String> lines()
    Liefert einen Stream von Strings. Im Fehlerfall wird keine geprüfte Ausnahme ausgelöst, sondern eine ungeprüfte Ausnahme vom Typ UncheckedIOException. Neu in Java 8.

 
Zum Seitenanfang

7.6.4LineNumberReader zählt automatisch Zeilen mit * Zur vorigen ÜberschriftZur nächsten Überschrift

Aus BufferedReader geht direkt die – bisher einzige – Unterklasse LineNumberReader hervor, die Zeilennummern zugänglich macht. Sie verfügt damit natürlich auch über readLine(). Mit getLineNumber() und setLineNumber(int) lässt sich aber zusätzlich auf die Zeilennummer zugreifen. Dass die Zeilennummer auch geschrieben werden kann, ist sicherlich ungewöhnlich, intern wird aber nur die Variable lineNumber geschrieben; der Datenzeiger wird nicht verändert.

Bei jedem read(…) untersuchen die Methoden, ob im Eingabestrom ein »\n«, »\r« oder eine Folge dieser beiden Zeichen vorkommt. Wenn dies der Fall ist, inkrementieren sie die Variable lineNumber. Zeilennummern beginnen bei 0.

Die Klassenhierarchie von LineNumberReader

Abbildung 7.8Die Klassenhierarchie von LineNumberReader

class java.io.LineNumberReader
extends BufferedReader
  • LineNumberReader(Reader in)
    Dekoriert einen gegebenen Reader.

  • LineNumberReader(Reader in, int sz)
    Dekoriert einen gegebenen Reader mit gegebener Puffergröße.

  • int getLineNumber()
    Liefert die aktuelle Zeilennummer.

  • void setLineNumber(int lineNumber)
    Setzt die aktuelle Zeilennummer.

 
Zum Seitenanfang

7.6.5Daten mit der Klasse PushbackReader zurücklegen * Zur vorigen ÜberschriftZur nächsten Überschrift

Die Klassen PushbackReader und PushbackInputStream können schon gelesene Eingaben wieder in den Strom zurücklegen. Das ist nützlich für so genannte vorausschauende Parser, die eine Wahl anhand des nächsten gelesenen Zeichens treffen. Mit den beiden Klassen kann dieses Vorschauzeichen wieder in den Eingabestrom gelegt werden, wenn der Parser den Weg doch nicht verfolgen möchte. Der nächste Lesezugriff liest dann nämlich dieses zurückgeschriebene Zeichen.

Die Filterklassen besitzen einen internen Puffer beliebiger Größe, in dem Symbole gespeichert werden, um sie später zurückholen zu können. Im Folgenden wollen wir uns nur mit dem PushbackReader beschäftigen; die Nutzung der Klasse PushbackInputStream ist ähnlich.

UML-Diagramm mit den PushbackXXX-Klassen

Abbildung 7.9UML-Diagramm mit den PushbackXXX-Klassen

class java.io.PushbackReader
extends FilterReader
  • PushbackReader(Reader in)
    Erzeugt einen PushbackReader aus dem Reader in mit der Puffergröße 1.

  • PushbackReader(Reader in, int size)
    Erzeugt einen PushbackReader aus dem Reader in mit der Puffergröße size.

Um ein Zeichen oder eine Zeichenfolge wieder in den Eingabestrom zu legen, wird die Methode unread(…) ausgeführt:

  • public void unread(int c) throws IOException

  • public void unread(char[] cbuf, int off, int len) throws IOException

  • public void unread(char[] cbuf) throws IOException
    Legt ein Zeichen oder ein Feld von Zeichen zurück in den Zeichenstrom.

PushbackReader ist ein Eingabefilter und die einzige Klasse, die direkt aus FilterReader abgeleitet ist.

Zeilennummern mit einem PushbackReader entfernen

Das nächste Programm demonstriert die Möglichkeiten eines PushbackReaders. Die Implementierung wirkt möglicherweise etwas gezwungen, sie zeigt jedoch, wie unread(char) eingesetzt werden kann. Das Programm löst folgendes Problem: Wir haben eine Textdatei (im Programm einfach als String über einen StringReader zur Verfügung gestellt), in der Zeilennummern mit dem String verbunden sind:

134Erste Zeile
234Zeile

Wir wollen nun die Zahlen vom Rest der Zeilen trennen. Dazu lesen wir so lange die Zahlen ein, bis ein Zeichen folgt, bei dem Character.isDigit(…) die Rückgabe false ergibt. Dann wissen wir, dass wir keine Ziffer mehr im Strom haben. Das Problem ist nun, dass zum Testen schon ein Zeichen mehr gelesen werden musste. In einem normalen Programm ohne die Option, das Zeichen zurücklegen zu können, würde das ungemütlich. Dieses Zeichen müsste dann gesondert behandelt werden, da es das erste Zeichen der neuen Eingabe ist und nicht mehr zur Zahl gehört. Doch an Stelle dieser Sonderbehandlung legen wir es einfach wieder mit unread(char) in den Datenstrom, und dann kann der nachfolgende Programmcode einfach so weitermachen, als ob nichts gewesen wäre:

Listing 7.17com/tutego/insel/io/stream/PushbackReaderDemo.java, main()

String s = "134Erste Zeile\n234Zeile";

PushbackReader in = new PushbackReader( new StringReader(s) );

for ( int c; ; ) {
try {
int number = 0;

// Read until no digit

while ( Character.isDigit((char)(c = in.read())) )
number = number * 10 + Character.digit( c, 10 );

if ( c == –1 ) // Ende erreicht? Dann aufhören
break;

in.unread( c ); // Gelesenes Zeichen zurücklegen

System.out.print( number + ":" );

while ( (c = in.read()) != –1 ) {
System.out.print( (char) c );

if ( c == '\n' )
break;
}
if ( c == –1 )
break;
}
catch ( EOFException e ) {
break;
}
}

PushbackReader und das fehlende readLine()

Da PushbackReader nicht von BufferedReader abgeleitet ist und auch selbst keine Methode readLine() anbietet, müssen wir mit einer kleinen Schleife selbst Zeilen lesen. Im Bedarfsfall muss die Zeichenkombination »\r\n« gelesen werden. So wie die Methode von uns jetzt programmiert ist, ist sie auf Plattformen beschränkt, die nur ein einziges Endezeichen einfügen. Doch warum nutzen wir nicht readLine()? Wer nun auf die Idee kommt, folgende Zeilen zu schreiben, um doch in den Genuss der Methode readLine() zu kommen, ist natürlich auf dem Holzweg:

StringReader sr = new StringReader( s );
BufferedReader br = new BufferedReader ( sr );
PushbackReader in = new PushbackReader( br );
...
br.readLine(); // Achtung, br!!

Wenn wir dem PushbackReader das Zeichen wiedergeben, dann arbeitet der BufferedReader genau eine Ebene darüber und bekommt vom Zurückgeben nichts mit. Daher ist es sehr gefährlich, die Verkettung zu umgehen. Im konkreten Fall wird das unread(char) nicht durchgeführt, und das erste Zeichen nach der Zahl fehlt.

 
Zum Seitenanfang

7.6.6DataOutputStream/DataInputStream * Zur vorigen ÜberschriftZur nächsten Überschrift

Während der OutputStream nur einzelne Bytes bzw. Byte-Felder schreibt und der InputStream aus einer Eingabe Bytes lesen kann, erweitern die Klassen DataOutputStream und DataInputStream diese Schreib- und Lesefähigkeit um primitive Datentypen. Die Vorgaben bekommen sie aus DataOutput und DataInput, die wir schon bei RandomAccessFile sahen. Wichtige Methoden sind zum Beispiel writeChar(char), writeInt(int), writeUTF(char) oder readUnsignedByte(), readLong() und readFully(byte[]).

[»]Hinweis

Der DataInputStream implementiert DataInput und erweitert FilterInputStream, was wiederum ein InputStream ist. Bei InputStream ist für die Methode read() eine Rückgabe von –1 vermerkt, wenn kein Byte mehr gelesen werden kann. Der DataInputStream muss aber mit einer Methode wie readLong() 8 Byte aus der Eingabe lesen.

Sind zum Beispiel nur 7 Byte im Strom und das letzte Byte kann nicht gelesen werden, ist das Ergebnis eine EOFException und nicht –1 (das kann auch nicht sein, da –1 im Datenstrom stehen könnte). Eine IOException kann auch ausgelöst werden, aber nicht, wenn Daten fehlen, sondern wenn beim Lesen der einzelnen Bytes die Ausnahme aufkam.

 
Zum Seitenanfang

7.6.7Basisklassen für Filter * Zur vorigen ÜberschriftZur nächsten Überschrift

Als Basisklassen für existierende Filter – und insbesondere für eigene Filter – sieht die Standardbibliothek folgende Klassen vor:

  • FilterInputStream

  • FilterOutputStream für die Binärseite

  • FilterReader

  • FilterWriter für die Zeichenseite

Eine konkrete Filterklasse überschreibt nötige Methoden ihrer Basisklassen (also vom InputStream, OutputStream, Reader oder Writer) und ersetzt diese durch neue Methoden mit erweiterter Funktionalität. Die folgende Abbildung stellt die zentralen Filter vor.

UML-Diagramm mit ausgewählten Filterklassen

Abbildung 7.10UML-Diagramm mit ausgewählten Filterklassen

Am UML-Diagramm fällt besonders auf, dass jeder Filter zum einen selbst ein Stream ist und zum anderen einen Stream verwaltet. Damit nimmt er Daten entgegen und leitet sie gleich weiter. Das ist ein bekanntes Design-Pattern und nennt sich Dekorator.

 
Zum Seitenanfang

7.6.8Die Basisklasse FilterWriter * Zur vorigen ÜberschriftZur nächsten Überschrift

Die Basis für eigene zeichenorientierte Filter, die vor dem Verarbeiten vom Client modifiziert werden sollen, ist die abstrakte Klasse FilterWriter. Wir übergeben im Konstruktor ein Writer-Objekt, an das die späteren Ausgaben weitergeleitet werden. Das Konstruktor-Argument wird in dem protected-Attribut out des FilterWriter-Objekts gesichert. In der Unterklasse greifen wir darauf zurück, denn dorthin schickt der Filter seine Ausgaben.

Die Standardimplementierung der Klasse FilterWriter überschreibt drei der write(…)-Methoden so, dass die Ausgaben an den im Konstruktor übergebenen Writer gehen.

abstract class java.io.FilterWriter
extends Writer
  • protected Writer out
    Der Ausgabestrom, an den die Daten geschickt werden. Er wird dem Konstruktor übergeben, der ihn in out speichert.

  • protected FilterWriter(Writer out)
    Erzeugt einen neuen filternden Writer.

  • void write(int c)
    Schreibt ein einzelnes Zeichen.

  • void write(char[] cbuf, int off, int len)
    Schreibt einen Teil eines Zeichenfeldes.

  • void write(String str, int off, int len)
    Schreibt einen Teil eines Strings.

  • void close()
    Schließt den Stream.

  • void flush()
    Leert den internen Puffer des Streams.

Die Klasse ist abstrakt, also können keine direkten Objekte erzeugt werden. Dennoch gibt es einen protected-Konstruktor, der für Unterklassen wichtig ist. Abgeleitete Klassen bieten in der Regel selbst einen Konstruktor mit dem Parameter vom Typ Writer an und rufen im Rumpf mit super(write) den geschützten Konstruktor der Oberklasse FilterWriter auf. Über die initialisierte geschützte Objektvariable out kommen wir dann an diesen Ur-Writer.

 
Zum Seitenanfang

7.6.9Ein LowerCaseWriter * Zur vorigen ÜberschriftZur nächsten Überschrift

Wir wollen im Folgenden einen Filter schreiben, der alle in den Strom geschriebenen Zeichen in Kleinbuchstaben umwandelt. Drei Dinge sind für einen eigenen FilterWriter nötig:

  • Die Klasse leitet sich von FilterWriter ab.

  • Unser Konstruktor nimmt als Parameter ein Writer-Objekt und ruft mit super(out) den Konstruktor der Oberklasse auf, also FilterWriter. Die Oberklasse speichert das übergebene Argument in der geschützten Objektvariablen out, sodass die Unterklassen darauf zugreifen können.

  • Wir überlagern die drei write(…)-Methoden und eventuell noch close() und flush(). Unsere write(…)-Methoden führen dann die Filteroperationen aus und geben die wahren Daten an den Writer weiter.

Vererbungsbeziehungen der neuen Klasse LowerCaseWriter

Abbildung 7.11Vererbungsbeziehungen der neuen Klasse LowerCaseWriter

Listing 7.18com/tutego/insel/io/stream/LowerCaseWriterDemo.java, LowerCaseWriter

class LowerCaseWriter extends FilterWriter {

public LowerCaseWriter( Writer writer ) {
super( writer );
}

@Override
public void write( int c ) throws IOException {
out.write( Character.toLowerCase((char)c) );
}

@Override
public void write( char[] cbuf, int off, int len ) throws IOException {
write( String.valueOf( cbuf ), off, len );
}

@Override
public void write( String s, int off, int len ) throws IOException {
out.write( s.toLowerCase(), off, len );
}
}

Und die Nutzung sieht dann so aus:

Listing 7.19com/tutego/insel/io/stream/LowerCaseWriterDemo.java, LowerCaseWriterDemo main()

StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter( new LowerCaseWriter( sw ) );
pw.println( "Eine Zeile für klein und groß" );
System.out.println( sw.toString() );
 
Zum Seitenanfang

7.6.10Eingaben mit der Klasse FilterReader filtern * Zur vorigen ÜberschriftZur nächsten Überschrift

Wie das Schachteln von Ausgabeströmen, so ist auch das Verbinden mehrerer Eingabeströme möglich. Als abstrakte Basiszwischenklasse existiert hier FilterReader, die ein Reader-Objekt im Konstruktor übergeben bekommt. Dieser sichert das Argument in der protected-Variablen in. (Das ist das gleiche Prinzip wie bei den anderen FilterXXX-Klassen.) Der Konstruktor ist protected, da er von der Unterklasse mit super(reader) aufgerufen werden soll. Standardmäßig leiten die Methoden vom FilterReader die Methoden an den Reader aus der Variablen in weiter; das heißt etwa: Wenn der FilterReader geschlossen wird, wird der Aufruf in.close() ausgeführt. Aus diesem Grund muss der FilterReader auch alle Methoden von Reader überschreiben, da ja eine Umleitung stattfindet.

abstract class java.io.FilterReader
extends Reader
  • protected Reader in
    Der Zeicheneingabestrom oder null, wenn der Strom geschlossen wurde.

  • protected FilterReader(Reader in)
    Erzeugt einen neuen filternden Reader.

UML-Diagramm von FilterReader

Abbildung 7.12UML-Diagramm von FilterReader

Die Methoden read(), read(char[] cbuf, int off, int len), skip(long n), ready(), markSupported(), mark(int readAheadLimit), reset() und close() werden überschrieben und leiten die Aufrufe direkt an Reader weiter. Lösen die Methoden eine Ausnahme aus, leitet der FilterReader sie standardmäßig an uns weiter.

 
Zum Seitenanfang

7.6.11Anwendungen für FilterReader und FilterWriter * Zur vorigen ÜberschriftZur nächsten Überschrift

Unsere nächste Klasse bringt uns etwas näher an das HTML-Format heran. Wir wollen eine Klasse HTMLWriter entwerfen, die FilterWriter erweitert und Textausgaben in HTML konvertiert. In HTML werden Tags eingeführt, die vom Browser erkannt und besonders behandelt werden. Findet etwa der Browser im HTML-Text eine Zeile der Form <strong>Dick</strong>, so stellt er den Inhalt »Dick« in fetter Schrift dar, da das <strong>-Element den Zeichensatz umstellt. Alle Tags werden in spitzen Klammern geschrieben. Daraus ergibt sich, dass HTML einige spezielle Zeichenfolgen (Entities genannt) verwendet. Wenn diese Zeichen auf der HTML-Seite dargestellt werden, muss dies durch spezielle Zeichensequenzen geschehen:

  • < wird zu &lt;

  • > wird zu &gt;

  • & wird zu &amp;

Kommen diese Zeichen im Quelltext vor, so muss unser HTMLWriter diese Zeichen durch die entsprechende Sequenz ersetzen. Andere Zeichen sollen nicht ersetzt werden.

Den Browsern ist die Struktur der Zeilen in einer HTML-Datei egal. Sie formatieren wiederum nach speziellen Tags. Zeilenvorschübe etwa werden mit <br/> eingeleitet. Unser HTMLWriter soll zwei leere Zeilen durch das Zeilenvorschub-Element <br/> markieren.

HTML-Dokument schreiben

Alle sauberen HTML-Dateien haben einen wohldefinierten Anfang und ein wohldefiniertes Ende. Das folgende kleine HTML-Dokument ist wohlgeformt und zeigt, was unser Programm später erzeugen soll:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html><head><title>Superkreativer Titel</title></head>
<body><p>
Und eine Menge von Sonderzeichen: &lt; und &gt; und &amp;
Zweite Zeile
<br/>
Leerzeile
Keine Leerzeile danach
</p></body></html>

Der Titel der Seite sollte im Konstruktor übergeben werden können. Hier ist nun das Programm für den HTMLWriter:

Listing 7.20com/tutego/insel/io/stream/HTMLWriter.java

package com.tutego.insel.io.stream;

import java.io.*;

class HTMLWriter extends FilterWriter {

private boolean newLine;

/**
* Creates a new filtered HTML writer with a title for the web page.
*
* @param out a Writer object to provide the underlying stream.
* @throws IOException if the header cannot be written
*/
public HTMLWriter( Writer out, String title ) throws IOException {
super( out );

out.write( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"" +
" \"http://www.w3.org/TR/html4/strict.dtd\">\n" );
out.write( "<html><head><title>" + title + "</title></head>\n<body><p>\n" );
}

/**
* Creates a new filtered HTML writer with no title for the web page.
*
* @param out a Writer object to provide the underlying stream.
*/
public HTMLWriter( Writer out ) {
super( out );
}

/**
* Writes a single character.
*/
@Override
public void write( int c ) throws IOException {
switch ( c ) {
case '<':
out.write( "&lt;" );
newLine = false;
break;
case '>':
out.write( "&gt;" );
newLine = false;
break;
case '&':
out.write( "&amp;" );
newLine = false;
break;
case '\n':
if ( newLine ) {
out.write( "<br/>\n" );
newLine = false;
}
else
out.write( "\n" );
newLine = true;
break;
case '\r':
break; // ignore

default :
out.write( c );
newLine = false;
}
}

/**
* Writes a portion of an array of characters.
*
* @param cbuf Buffer of characters to be written
* @param off Offset from which to start reading characters
* @param len Number of characters to be written
* @exception IOException If an I/O error occurs
*/
@Override
public void write( char[] cbuf, int off, int len ) throws IOException {
for ( int i = off; i < len; i++ )
write( cbuf[ i ] );
}

/**
* Writes a portion of a string.
*
* @param str String to be written.
* @param off Offset from which to start reading characters
* @param len Number of characters to be written
* @exception IOException If an I/O error occurs
*/
@Override
public void write( String str, int off, int len ) throws IOException {
for ( int i = off; i < len; i++ )
write( str.charAt( i ) );
}

/**
* Closes the stream.
*
* @throws IOException If the prolog can not be written or the underlying stream * not be closed
*/
@Override
public void close() throws IOException {
try {
out.write( "</p></body></html>" );
}
finally {
out.close(); // Ignoriere, falls out.close() und out.write() knallt
}
}
}

Ein Demo-Programm soll die aufbereiteten Daten in einen StringWriter schreiben:

Listing 7.21com/tutego/insel/io/stream/HTMLWriterDemo.java, main()

StringWriter sw = new StringWriter();
HTMLWriter html = new HTMLWriter( sw, "Superkreativer Titel" );
PrintWriter pw = new PrintWriter( html );
pw.println( "Und eine Menge von Sonderzeichen: < und > und &" );
pw.println( "Zweite Zeile" );
pw.println();
pw.println( "Leerzeile" );
pw.println( "Keine Leerzeile danach" );
pw.close();
System.out.println( sw.toString() );

HTML-Tags mit einem speziellen Filter überlesen

Unser nächstes Beispiel ist eine Klasse, die den FilterReader so erweitert, dass HTML-Tags überlesen werden. Die Klasse FilterReader deklariert den notwendigen Konstruktor zur Annahme des Readers, der die wirklichen Daten liefert, und überschreibt zwei read(…)-Methoden. Die read()-Methode ohne Parameter – die ein int für ein gelesenes Zeichen zurückgibt – legt einfach ein 1 Zeichen großes Feld an und ruft dann die zweite überschriebene read(char[], int , int)-Methode auf, die die Daten in ein Feld liest. Da dieser Methode neben dem Feld auch noch die Größe übergeben werden kann, müssen wirklich so viele Zeichen gelesen werden. Es reicht einfach nicht aus, die übergebene Anzahl von Zeichen vom tiefer liegenden Reader zu lesen, sondern hier müssen wir beachten, dass eingestreute Tags nicht zählen. Die Zeichenkette <p>Hallo<p> ist ja nur fünf Zeichen lang und nicht elf!

Listing 7.22com/tutego/insel/io/stream/HTMLReader.java

package com.tutego.insel.io.stream;

import java.io.*;

public class HTMLReader extends FilterReader {
private boolean inTag = false;

public HTMLReader( Reader in ) {
super( in );
}

@Override
public int read() throws IOException {
char[] buf = new char[1];
return read( buf, 0, 1 ) == –1 ? –1 : buf[0];
}

@Override
public int read( char[] cbuf, int off, int len ) throws IOException {
int numchars = 0;

while ( numchars == 0 ) {
numchars = in.read( cbuf, off, len );

if ( numchars == –1 ) // EOF?
return –1;

int last = off;

for ( int i = off; i < off + numchars; i++ ) {
if ( ! inTag ) {
if ( cbuf[i] == '<' )
inTag = true;
else
cbuf[last++] = cbuf[i];
}
else if ( cbuf[i] == '>' )
inTag = false;
}
numchars = last – off;
}
return numchars;
}
}

Ein Beispielprogramm soll die Daten aus einem StringReader ziehen. Der HTMLReader bekommt diesen StringReader und wird selbst von Scanner genutzt, damit wir die komfortable nextLine()-Methode nutzen können. Da hier keine externen Ressourcen vorkommen, müssen wir nichts schließen, und ein try mit Ressourcen kann entfallen.

Listing 7.23com/tutego/insel/io/stream/HTMLReaderDemo.java, main()

String s = "<html>Hallo! <b>Ganz schön fett.</b> "
+ "Ah, wieder normal.</html>";

Reader sr = new StringReader( s );
Reader hr = new HTMLReader( sr );
Scanner scanner = new Scanner( hr );
while ( scanner.hasNextLine() )
System.out.println( scanner.nextLine() );

Es produziert dann die einfache Ausgabe:

Hallo! Ganz schön fett. Ah, wieder normal.

 


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java SE 8 Standard-Bibliothek Java SE 8 Standard-Bibliothek
Jetzt Buch bestellen

 Buchempfehlungen
Zum Rheinwerk-Shop: Java ist auch eine Insel
Java ist auch eine Insel


Zum Rheinwerk-Shop: Professionell entwickeln mit Java EE 8
Professionell entwickeln mit Java EE 8


Zum Rheinwerk-Shop: Besser coden
Besser coden


Zum Rheinwerk-Shop: Entwurfsmuster
Entwurfsmuster


Zum Rheinwerk-Shop: IT-Projektmanagement
IT-Projektmanagement


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2018
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern