Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 12 Datenströme und Dateien
gp 12.1 Datei und Verzeichnis
gp 12.1.1 Dateien und Verzeichnisse mit der Klasse File
gp 12.1.2 Dateieigenschaften und -attribute
gp 12.1.3 Sicherheitsprüfung
gp 12.1.4 Umbenennen und Verzeichnisse anlegen
gp 12.1.5 Die Wurzel aller Verzeichnisse
gp 12.1.6 Verzeichnisse listen und Dateien filtern
gp 12.1.7 Dateien und Verzeichnisse löschen
gp 12.1.8 Implementierungsmöglichkeiten für die Klasse File
gp 12.1.9 Verzeichnisse nach Dateien rekursiv durchsuchen
gp 12.2 Dateien mit wahlfreiem Zugriff
gp 12.2.1 Ein RandomAccessFile öffnen
gp 12.2.2 Aus dem RandomAccessFile lesen
gp 12.2.3 Schreiben
gp 12.2.4 Die Länge des RandomAccessFile
gp 12.2.5 Hin und her in der Datei
gp 12.3 Übersicht über wichtige Stream- und WriterReader
gp 12.3.1 Die abstrakten Basisklassen
gp 12.3.2 Übersicht über Ein-/Ausgabeklassen
gp 12.4 Eingabe- und Ausgabe-Klassen: InputStream und OutputStream
gp 12.4.1 Die Klasse OutputStream
gp 12.4.2 Ein Datenschlucker
gp 12.4.3 Anwendung der Klasse FileOutputStream
gp 12.4.4 Die Eingabeklasse InputStream
gp 12.4.5 Anwenden der Klasse FileInputStream
gp 12.4.6 Kopieren von Dateien
gp 12.4.7 Daten filtern durch FilterInputStream und FilterOutputStream
gp 12.4.8 Der besondere Filter PrintStream
gp 12.4.9 System.in und System.out
gp 12.4.10 Bytes in den Strom schreiben mit ByteArrayOutputStream
gp 12.4.11 Ströme zusammensetzen mit SequenceInputStream
gp 12.5 Ressourcen wie Grafiken aus dem Klassenpfad und aus Jar-Archiven laden
gp 12.6 Die Unterklassen von Writer
gp 12.6.1 Die abstrakte Klasse Writer
gp 12.6.2 Datenkonvertierung durch den OutputStreamWriter
gp 12.6.3 In Dateien schreiben mit der Klasse FileWriter
gp 12.6.4 StringWriter und CharArrayWriter
gp 12.6.5 Writer als Filter verketten
gp 12.6.6 Gepufferte Ausgabe durch BufferedWriter
gp 12.6.7 Ausgabemöglichkeiten durch PrintWriter erweitern
gp 12.6.8 Daten mit FilterWriter filtern
gp 12.7 Die Klassen um Reader
gp 12.7.1 Die abstrakte Basisklasse Reader
gp 12.7.2 Automatische Konvertierungen mit dem InputStreamReader
gp 12.7.3 Dateien lesen mit der Klasse FileReader
gp 12.7.4 StringReader und CharArrayReader
gp 12.8 Schachteln von Eingabe-Streams
gp 12.8.1 Gepufferte Eingaben mit der Klasse BufferedReader
gp 12.8.2 LineNumberReader zählt automatisch Zeilen mit
gp 12.8.3 Eingaben filtern mit der Klasse FilterReader
gp 12.8.4 Daten mit der Klasse PushbackReader zurücklegen
gp 12.9 Kommunikation zwischen Threads mit Pipes
gp 12.9.1 PipedOutputStream und PipedInputStream
gp 12.9.2 PipedWriter und PipedReader
gp 12.10 Datenkompression
gp 12.10.1 Die Java-Unterstützung beim Komprimieren und Zusammenpacken
gp 12.10.2 Datenströme komprimieren
gp 12.10.3 Zip-Archive
gp 12.11 Prüfsummen
gp 12.11.1 Die Schnittstelle Checksum
gp 12.11.2 Die Klasse CRC32
gp 12.11.3 Die Adler32-Klasse
gp 12.12 Persistente Objekte und Serialisierung
gp 12.12.1 Objekte speichern
gp 12.12.2 Objekte lesen
gp 12.12.3 Die Schnittstelle Serializable
gp 12.12.4 Nicht serialisierbare Attribute mit transient aussparen
gp 12.12.5 Das Abspeichern selbst in die Hand nehmen
gp 12.12.6 Tiefe Objektkopien
gp 12.12.7 Versionenverwaltung und die SUID
gp 12.12.8 Wie die ArrayList serialisiert
gp 12.12.9 Serialisieren in XML-Dateien
gp 12.12.10 JSX (Java Serialization to XML)
gp 12.12.11 XML-API von Sun
gp 12.13 Zugriff auf SMB-Server
gp 12.13.1 jCIFS
gp 12.14 Tokenizer
gp 12.14.1 StreamTokenizer
gp 12.14.2 CSV (Comma Separated Values)-Dateien verarbeiten
gp 12.15 Die Logging-API


Galileo Computing

12.6 Die Unterklassen von Writerdowntop

Alle Klassen, die Unicode-Zeichen schreiben, basieren auf der abstrakten Klasse Writer. Aus Writer leiten sich später Klassen ab, die konkrete Ausgabegeräte ansprechen oder Daten filtern. Folgende Tabelle zeigt die aus Writer abgeleiteten Klassen:


Klasse Bedeutung
OutputStreamWriter Abstrakte Basisklasse für alle Writer, die einen Zeichen-Stream in einen Byte-Stream umwandeln
FilterWriter Abstrakte Basisklasse für Filterobjekte
PrintWriter Ausgabe der primitiven Datentypen
BufferedWriter Writer, der puffert
StringWriter Writer, der in einen String schreibt
CharArrayWriter Writer, der in ein Zeichenfeld schreibt
PipedWriter Writer zur Ausgabe in einen passenden PipedReader

Tabelle 12.3 Übersicht der von Writer direkt abgeleiteten Klassen

Wir lernen noch die Klasse FileWriter kennen, die nicht direkt aus Writer hervorgeht. Hier hängt noch eine weitere Klasse dazwischen, die Unterklasse von Writer ist: OutputStreamWriter.


Galileo Computing

12.6.1 Die abstrakte Klasse Writerdowntop

Basis für alle wichtigen Klassen ist die abstrakte Basisklasse Writer.


abstract class java.io.Writer

gp protected Writer( Object lock )
Erzeugt einen Writer-Stream, der sich mit dem übergebenen Synchronisations-Objekt initialisiert. Ist die Referenz null, so gibt es eine NullPointerException.
gp protected Writer()
Erzeugt einen Writer-Stream, der sich selbst als Synchronisations-Objekt nutzt. Der Konstruktor ist für die Unterklassen interessant, die kein eigenes Lock-Objekt zuordnen wollen.
gp void write( int c ) throws IOException
Schreibt ein einzelnes Zeichen. Von der 32-Bit-Ganzzahl wird der niedrige Teil (16 Bit des ints) geschrieben.
gp void write( char cbuf[] ) throws IOException
Schreibt ein Feld von Zeichen.
gp abstract void write( char cbuf[], int off, int len ) throws IOException
Schreibt len Zeichen des Felds cbuf ab der Position off.
gp void write( String str ) throws IOException
Schreibt einen String.
gp void write( String str, int off, int len ) throws IOException
Schreibt len Zeichen der Zeichenkette str ab der Position off.
gp abstract void flush() throws IOException
Schreibt den internen Puffer. Hängt verschiedene flush()-Aufrufe in einer Kette zusammen, die sich aus der Abhängigkeit der Objekte ergibt. So werden alle Puffer geschrieben.
gp abstract void close() throws IOException
Schreibt den gepufferten Strom und schließt ihn. Nach dem Schließen durchgeführte write()- oder flush()-Aufrufe bringen eine IOException mit sich. Ein zusätzliches close() wirft keine Exception.

Wie die abstrakten Methoden genutzt und überschrieben werden

Uns fällt auf, dass von den sieben Methoden lediglich flush(), close() und write(char[], int, int) abstrakt sind. Zum einen bedeutet dies, dass konkrete Unterklassen nur diese Methoden implementieren müssen, und zum anderen, dass die übrigen write()-Funktionen auf die eine überschriebene Implementierung zurückgreifen. Werfen wir daher ein Blick auf die Nutznießer:

public void write(int c) throws IOException {
  synchronized (lock) {
    if (writeBuffer == null)
      writeBuffer = new char[writeBufferSize];
    writeBuffer[0] = (char) c;
    write(writeBuffer, 0, 1);
  }
}

Wird ein Zeichen geschrieben, so wird zunächst einmal nachgesehen, ob schon jemand einen temporären Puffer eingerichtet hat. Ein schöner Trick, denn Speicherbeschaffung ist nicht ganz billig. Wenn nicht, dann erzeugt die Funktion zunächst ein Array mit der Größe von 1.024 Zeichen. (Dies ist die eingestellte Puffer-Größe.) Dann schreibt write(int) das Zeichen in den Puffer und ruft die abstrakte Methode auf. Ist der Parameter ein Feld, so muss lediglich die Größe an die abstrakte Methode übergeben werden. Alle Schreiboperationen sind mit einem lock-Objekt synchronisiert und können sich demnach nicht in die Quere kommen. Die Synchronisation wird entweder durch ein eigenes lock-Objekt durchgeführt, das dann im Konstruktor angegeben werden muss, oder die Klasse verwendet das this-Objekt der Writer-Klasse als Sperr-Objekt.

Schreiben einer Zeichenkette

Um einen Teil einer Zeichenkette zu schreiben, wird schon etwas mehr Aufwand betrieben. Ist der interne Puffer zu klein, wird ein neuer angelegt. Geschickt wird dieser Puffer cbuf gleich im Objekt gehalten, damit auch andere Funktionsaufrufe davon profitieren können. So wird vorgebeugt, dass vielleicht große Blöcke temporären Speichers verwendet werden.

public void write(String str, int off, int len) throws IOException
{
  synchronized (lock) {
    char cbuf[];
    if (len <= writeBufferSize) {
      if (writeBuffer == null)
        writeBuffer = new char[writeBufferSize];
      cbuf = writeBuffer;
    } else
      cbuf = new char[len];
    str.getChars(off, (off + len), cbuf, 0);
    write(cbuf, 0, len);
  }
}

Wir sehen bei der letzten übrig gebliebenen Funktion, dass sie write(String) in schlauer Weise nutzt.

public void write(String str) throws IOException {
  write(str, 0, str.length());
}

Es liegt nun an den Unterklassen, diese Methoden zu überschreiben. Wir haben gesehen, dass lediglich die eine abstrakte Methode zwingend überschrieben werden muss, jedoch macht es durchaus Sinn, etwa zugunsten einer höheren Geschwindigkeit, auch die anderen Funktionen zu überschreiben.


Galileo Computing

12.6.2 Datenkonvertierung durch den OutputStreamWriterdowntop

Die Klasse OutputStreamWriter ist sehr interessant, da sie Konvertierungen der Zeichen nach einer Zeichenkodierung vornimmt. So wird sie für Ausgaben in Dateien, unterstützt durch die einzige Unterklasse FileWriter, noch wichtiger. Jeder OutputStreamWriter konvertiert so Zeichenströme von einer Zeichenkodierung (etwa EBCDIC) in eine andere (etwa Latin -1). Die Zeichenkodierung kann im Konstruktor eines OutputStreamWriter-Objekts angegeben werden. Ohne Angabe ist es der Standardkonvertierter, der in den Systemeigenschaften unter dem Schlüssel file.encoding geschrieben ist. Die Kodierung der Zeichen wird über einen StreamEncoder vorgenommen. Die Klasse liegt unter dem Paket sun.nio.cs.


class java.io.OutputStreamWriter
extends Writer

gp OutputStreamWriter( OutputStream out )
Erzeugt einen OutputStreamWriter, der die Standardkodierung verwendet.
gp OutputStreamWriter( OutputStream out, Charset|CharsetEncoder cs )
Erzeugt einen OutputStreamWriter mit einem Charset oder einen CharsetEncoder. Diese Objekte gibt es erst seit der Version 1.4.
gp OutputStreamWriter( OutputStream out, Charset charset )
Erzeugt einen OutputStreamWriter mit einem Charset. Charset-Objekte gibt es erst seit der Version 1.4.
gp OutputStreamWriter( OutputStream out, String enc )
Erzeugt einen OutputStreamWriter mit der vorgegebenen Kodierung.
gp void close()
Schließt den Datenstrom.
gp void flush()
Schreibt den gepufferten Strom.
gp String getEncoding()
Liefert die Kodierung des Datenstroms als String.
gp void write( char cbuf[], int off, int len )
Schreibt Zeichen des Felds.
gp void write( int c )
Schreibt ein einzelnes Zeichen.
gp void write( String str, int off, int len )
Schreibt den Teil eines Strings.

Galileo Computing

12.6.3 In Dateien schreiben mit der Klasse FileWriterdowntop

OutputStreamWriter ist die Basisklasse für die konkrete Klasse FileWriter, einer Klasse, die die Ausgabe in eine Datei erlaubt. FileWriter muss keine Methoden überschreiben, und so fügt die Klasse nur fünf Konstruktoren hinzu, damit eine Datei geöffnet werden kann.

Nachfolgendes Programm erstellt die Datei fileWriter.txt und schreibt eine Textzeile hinein. Da der Konstruktor und die write()-Methode eine IOException in dem Fall auswerfen, wenn ein Öffnen nicht möglich ist, müssen wir einen try/catch-Block um die Anweisungen setzen.

Listing 12.14 FileWriterDemo.java

import java.io.*;
public class FileWriterDemo
{
  public static void main( String args[] )
  {
    FileWriter fw = null;
    try
    {
      fw = new FileWriter( "fileWriter.txt" );
      fw.write( "Hallo Welt geht in eine Datei" );
    }
    catch ( IOException e ) {
      System.out.println( "Konnte Datei nicht erstellen" );
    }
    finally {
      try {
        if ( fw != null ) fw.close();
      } catch (IOException e) {}
    }
  }
}

Hinter diesen Konstruktoren verbirgt sich ein FileOutputStream-Objekt. So konvertieren die write()-Methoden die Zeichenströme, aber letztendlich schreibt FileOutputStream die Daten.

public class FileWriter extends OutputStreamWriter
{
  public FileWriter(String fileName) throws IOException {
    super(new FileOutputStream(fileName));
  }
  public FileWriter(String fileName, boolean append)
      throws IOException {
    super(new FileOutputStream(fileName, append));
  }
  ...
}

Mit diesen Konstruktoren kann nun eine Datei geöffnet werden. Der einfachste Weg geht über den Dateinamen. Existiert die Datei schon, deren Namen wir übergeben, so wird die Datei gelöscht. Um die Daten hinten anzuhängen, sollten wir als zweiten Parameter true angeben. Eine weitere Möglichkeit, Daten hinten anzuhängen, bietet die Klasse RandomAccessFile oder FileOutputStream.


class java.io.FileWriter
extends OutputStreamWriter

gp FileWriter( File file )
FileWriter( FileDescriptor fd )
FileWriter( String filename )
Erzeugt einen Ausgabestrom zum Schreiben in eine Datei.
gp FileWriter( File file, boolean append )
FileWriter( String filename, boolean append )
Erzeugt einen Ausgabestrom, hängt die Daten an eine existierende Datei an.

Galileo Computing

12.6.4 StringWriter und CharArrayWriterdowntop

Zwei interessante Klassen sind StringWriter und CharArrayWriter. Sie sind ebenfalls von Writer abgeleitet, schreiben jedoch die Ausgabe nicht in eine Datei, sondern in einen StringBuffer beziehungsweise in ein Zeichen-Array. Die Felder werden automatisch vergrößert.

StringWriter

In den folgenden Programmzeilen konvertieren wir den StringWriter noch zu einem PrintWriter, damit wir die komfortable println()-Methode verwenden können.

StringWriter buffer = new StringWriter();
PrintWriter out = new PrintWriter( buffer );
out.println( "Ulli ist lieb" );
out.println( "Quatsch" );
String result = buffer.toString();

Hier findet der parameterlose Konstruktor Verwendung. Er legt einen StringWriter mit der Größe 16 an (Standardgröße eines StringBuffer-Objekts). Daneben existiert aber noch ein Konstruktor mit dem Parameter int. Dieser legt dann die anfängliche Größe fest. Wenn unser StringWriter größer als 16 wird, und das ist wahrscheinlich, sollte immer der zweite Konstruktor aus Performanzgründen Verwendung finden. So muss sich das interne StringBuffer-Objekt bei wiederholten write()-Aufrufen nicht immer in der Größe ändern. Mit den Funktionen getBuffer() und toString() lesen wir den Inhalt wieder aus. Die Methoden unterscheiden sich darin, dass getBuffer() ein StringBuffer-Objekt zurückgibt und toString() das gewohnte String-Objekt.


class java.io.StringWriter
extends Writer

gp StringWriter()
Erzeugt einen StringWriter mit der Startgröße 16.
gp StringWriter( int initialSize )
Erzeugt einen StringWriter mit der angegebenen Größe.
gp void close()
Schließt den StringWriter. Dennoch lassen sich nach dem Schließen trotzdem Zeichen schreiben, da die close()-Methode leer implementiert ist.
gp void flush()
Schreibt die gepufferten Daten in den Stream; leer implementiert.
gp StringBuffer getBuffer()
Liefert den internen StringBuffer, keine Kopie.
gp String toString()
Liefert den Puffer als String.
gp void write( char cbuf[], int off, int len )
Schreibt einen Teil der Zeichen des Felds.
gp void write( int c )
Schreibt ein einzelnes Zeichen.
gp void write( String str )
Schreibt einen String.
gp void write( String str, int off, int len )
Schreibt den Teil eines Strings.

CharArrayWriter

Neben StringWriter schreibt auch die Klasse CharArrayWriter Zeichen in einen Puffer, jedoch diesmal in ein Zeichenfeld. Sie bietet zudem drei zusätzliche Funktionen an: reset(), size() und writeTo().


class java.io.CharArrayWriter
extends Writer

gp CharArrayWriter()
Erzeugt einen neuen CharArrayWriter.
gp CharArrayWriter( int initialSize )
Erzeugt einen neuen CharArrayWriter mit einer Standardgröße.
gp void close()
Schließt den Stream.
gp void flush()
Leert den Stream.
gp void reset()
Setzt den internen Puffer zurück, so dass das CharArrayWriter-Objekt ohne neue Speicheranforderung genutzt werden kann.
gp int size()
Liefert die Größe des Puffers.
gp char[] toCharArray()
Gibt eine Kopie der Eingabedaten zurück. Es ist wirklich eine Kopie und keine Referenz.
gp String toString()
Konvertiert die Eingabedaten in einen String.
gp void write( char c[], int off, int len )
Schreibt Zeichen in den Puffer.
gp void write( int c )
Schreibt ein Zeichen in den Puffer.
gp void write( String str, int off, int len )
Schreibt einen Teil eines String in den Puffer.
gp void writeTo( Writer out )
Schreibt den Inhalt des Puffers in einen anderen Zeichenstrom. Diese Methode ist ganz nützlich, um die Daten weiterzugeben.

Galileo Computing

12.6.5 Writer als Filter verkettendowntop

Die Funktionalität der bisher vorgestellten Writer-Klassen reicht für den Alltag zwar aus, doch sind Ergänzungen gefordert, die den Nutzen oder die Fähigkeiten der Klassen erweitern. In Java gibt es die drei Klassen BufferedWriter, PrintWriter und FilterWriter, die einen Writer im Konstruktor erlauben und ihre Ausgabe an diesen weiterleiten. Die neue Klasse erweitert die Funktionalität, schreibt ihre Ausgabe in den alten Writer und die Klassen werden somit geschachtelt. Es lassen sich auch alle drei Klassen allesamt ineinander verschachteln. Das ist das Design-Pattern Dekorator.

Hier bauen wir zunächst einen FileWriter. Dieser sichert die Daten, die mittels write() gesendet werden, in einer Datei. Anschließend erzeugen wir einen BufferedWriter, der die Daten, die in die Datei geschrieben werden, erst einmal sammelt. Diesen BufferedWriter erweitern wir noch zu einem PrintWriter, da ein PrintWriter neue Schreibfunktionen besitzt, so dass wir nicht mehr nur auf write()-Methoden angewiesen sind, sondern die komfortablen print()-Funktionen nutzen können.


Beispiel Writer mit Filter verkettet, so dass die Ausgabe gepuffert wird.

Listing 12.15 CharArrayWriterDemo.java

import java.io.*;
public class CharArrayWriterDemo
{
  public static void main( String args[] )
  {
    try
    {
      FileWriter fw = new FileWriter("charArrayWriterDemoPuffer.txt");
      BufferedWriter bw = new BufferedWriter( fw );
      PrintWriter pw = new PrintWriter( bw );

      for ( int i = 1; i < 10000; i++ )
        pw.println( "Zeile " + i );
      pw.close();
    }
    catch ( IOException e ) {
      System.out.println( "Konnte Datei nicht erstellen" );
    }
  }
}


Galileo Computing

12.6.6 Gepufferte Ausgabe durch BufferedWriterdowntop

Die Klasse BufferedWriter hat die Aufgabe, Dateiausgaben, die mittels write() in den Stream geleitet werden, zu puffern. Dies ist immer dann nützlich, wenn viele Schreiboperationen gemacht werden, denn das Puffern macht die Dateioperationen wesentlich schneller, da so mehrere Schreiboperationen zu einer zusammengefasst werden. Um die Funktionalität eines Puffers zu erhalten, enthält ein BufferedWriter-Objekt einen internen Puffer, in dem die Ausgaben von write() zwischengespeichert werden. Standardmäßig ist dieser Puffer 8.192 Zeichen groß. 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 tatsächlicher write()-Aufrufe an das externe Gerät wird die Geschwindigkeit der Anwendung deutlich erhöht.

Abbildung
Hier klicken, um das Bild zu Vergrößern

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

Zusätzlich bietet die Klasse die Methode writeLine(), 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.

Das folgende Programm kopiert den Inhalt einer Textdatei und erzeugt dabei eine neue Datei:

Listing 12.16 ReadWriter.java

import java.io.*;
public class ReadWriter
{
  public static void main( String args[] )
  {
    if ( args.length != 2 ) {
      System.err.println( "usage: infile outfile\n" );
      System.exit( 1 );
    }
    BufferedReader in  = null;
    BufferedWriter out = null;
     
    try
    {
      in  = new BufferedReader( new FileReader( args[0] ) );
      out = new BufferedWriter( new FileWriter( args[1] ) );
      for ( String s; (s = in.readLine()) != null; )
      {
        out.write( s );
        out.newLine();
      }
    }
    catch ( IOException e ) {
      System.err.println( e );
    }
    finally {
      try { 
        if ( in != null ) in.close();
        if ( out != null ) out.close();
      } catch ( IOException e ) { }
    }
  }
}

Tipp Ein Geschwindigkeitstipp noch zum Schluss. Falls bekannt, sollte die Puffergröße des BufferedWriter (gleiches gilt für BufferedReader) mit der internen Puffergröße des Betriebssystems übereinstimmen. Hier können Geschwindigkeitsmessungen mit unterschiedlichen Puffergrößen die Lösung bringen.


class java.io.BufferedWriter
extends Writer

gp BufferedWriter( Writer out )
Erzeugt einen puffernden BufferedWriter mit der Puffergröße 8 k.
gp BufferedWriter( Writer out, int sz )
Erzeugt einen puffernden BufferedWriter mit der Puffergröße sz, die nicht kleiner 0 sein darf. Andernfalls gibt es eine IllegalArgumentException. Die Puffergröße 0 ist erlaubt, aber unsinnig.
gp void write( int c ) throws IOException
Schreibt ein einzelnes Zeichen.
gp void write( char cbuf[], int off, int len ) throws IOException
Schreibt einen Teil des Zeichenfelds.
gp void write( String s, int off, int len ) throws IOException
Schreibt einen Teil des Strings.
gp void newLine() throws IOException
Schreibt einen Zeilenvorschub, der in den Systemeigenschaften festgelegt ist.
gp void flush() throws IOException
Leert den Stream.
gp void close() throws IOException
Schließt den Stream. Kommen Anweisungen danach, wird eine IOException ausgelöst.

Ein nettes Detail am Rande bietet die Implementierung von BufferedWriter. Wir finden hier die Deklaration der Methode min(), die in den beiden write()-Methoden für Teilstrings beziehungsweise Teilfelder Verwendung findet.

/**
 * Our own little min method, to avoid loading java.lang.Math if we've run
 * out of file descriptors and we're trying to print a stack trace.
*/
private int min(int a, int b) {
  if (a < b) return a;
  return b;
}

Galileo Computing

12.6.7 Ausgabemöglichkeiten durch PrintWriter erweiterndowntop

Bisher waren die Ausgabemöglichkeiten eines Writer-Objekts nur beschränkt. Die Möglichkeit, mit write() Zeichen oder Zeichenfelder auszugeben, ist für den Alltag zu wenig. Erweitert wird dieses Objekt durch die Klasse PrintWriter. Sie bietet Methoden für alle primitiven Datentypen und für den Objekttyp. Dafür bietet der PrintWriter eine Reihe überladener Methoden mit dem Namen print() an. (PrintWriter ist also die Writer-Variante von PrintStream.) Damit auch die Ausgabe mit einem zusätzlichen Zeilenvorschub nicht von Hand umgesetzt werden muss, gibt es für alle print()-Methoden eine entsprechende Variante println(), bei der automatisch am Ende der Ausgabe ein Zeilenumbruch angehängt wird. println() existiert auch parameterlos, um nur einen Zeilenumbruch zu setzen. Das Zeilenvorschubzeichen ist wie immer an die Plattform angepasst. Die Ausgabe in einen PrintWriter ist so lange gepuffert, bis flush() ausgeführt wird. println() leert den Puffer, wenn der Konstruktor mit autoFlush gleich true aufgerufen wurde.


class java.io.PrintWriter
extends Writer

gp boolean checkError()
Schreibt die gepufferten Daten und prüft Fehler. Die Abfrage ist wichtig, da die Klasse keine Ein-/Ausgabe-Exceptions auswirft.
gp void close()
Schließt den Strom.
gp void flush()
Schreibt gepufferte Daten.
gp void print( boolean|char|char[]|double|float|int|Object|String )
Schreibt Boolean oder Zeichen, Array von Zeichen, Double, Float, Integer, Object oder String.
gp void println()
Schreibt Zeilenvorschubzeichen.
gp void println( boolean|char|char[]|double|float|int|Object|String )
Schreibt den Datentyp wie print() und schließt die Zeile mit Zeilenendezeichen ab.
gp void setError()
Zeigt an, dass ein Fehler auftrat.
gp void write( char[] | int | String )
Schreibt Array von Zeichen, Integer oder String.
gp void write( char buf[], int off, int len )
Schreibt einen Teil (len Zeichen) eines Arrays beginnend bei off.
gp void write( String s, int off, int len )
Schreibt einen Teilstring mit len Zeichen ab der Position off.

Keine der Methoden wirft eine IOException. Intern fängt der PrintWriter eine mögliche Exception ab und setzt ein internes Flag trouble, das aber im Programm keinen weiteren Einfluss besitzt. Daher musste auch die Methode write(String) neu definiert werden, da die Funktion write(String) der Writer-Klasse eine IOException wirft.

gp PrintWriter( OutputStream out )
Erzeugt einen neuen PrintWriter aus einem OutputStream, der nicht automatisch am Zeilenende den Puffer schreibt.
gp PrintWriter( OutputStream out, boolean autoFlush )
Erzeugt einen neuen PrintWriter aus einem OutputStream, der automatisch bei autoFlush gleich true am Zeilenende mittels println() den Puffer schreibt.
gp PrintWriter( Writer out )
Erzeugt einen neuen PrintWriter aus einem Writer, der nicht automatisch am Zeilenende den Puffer schreibt.
gp PrintWriter( Writer out, boolean autoFlush )
Erzeugt einen neuen PrintWriter aus einem OutputStream, der automatisch am Zeilenende mittels println() den Puffer leert, falls autoFlush=true ist.

Wir sehen, dass die Konstruktoren entweder mit einem OutputStream oder einem Writer arbeiten. Jeder Konstruktor existiert in zwei Varianten. Es gibt zusätzlich den Wahrheitsparameter autoflush, der, wenn er true ist, angibt, ob nach einem Zeilenumbruch automatisch flush() aufgerufen wird.


Galileo Computing

12.6.8 Daten mit FilterWriter filterntoptop

Die Architektur der Java-Klassen macht es leicht, auch eigene Filter zu programmieren. Basis ist dafür die abstrakte Klasse FilterWriter. Wir übergeben im Konstruktor ein Writer-Objekt, an das die Ausgaben weitergeleitet werden. Der Parameter wird in der Klasse in dem protected-Attribut out gesichert. In der Unterklasse greifen wir darauf zurück, denn dort schickt der Filter seine Ausgaben hin.

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

gp protected Writer out
Der Ausgabestrom, an den die Daten geschickt werden. Im Konstruktor gesetzt.
gp protected FilterWriter( Writer out )
Erzeugt einen neuen filternden Writer.
gp void close()
Schließt den Stream.
gp void flush()
Leert den internen Puffer des Streams.
gp void write( int c )
Schreibt ein einzelnes Zeichen.
gp void write( char cbuf[], int off, int len )
Schreibt einen Teil eines Zeichenfelds.
gp void write( String str, int off, int len )
Schreibt einen Teil eines Strings.

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.

Der Weg zum eigenen Filter

Damit nun unser eigener FilterWriter zum Leben erweckt werden kann, müssen wir nur drei Dinge beachten:

gp Unsere Klasse leitet sich von FilterWriter ab.
gp Unser Konstruktor bekommt als Parameter ein Writer-Objekt und ruft mit super(out) den Konstruktor der Oberklasse, also FilterWriter, auf. Die Oberklasse speichert den Parameter in der geschützten Objektvariablen out, so dass die Unterklassen darauf zugreifen können.
gp Wir überlagern die drei write()-Methoden und eventuell noch die close()-Methode. Unsere write()-Methoden führen dann die Filterfunktionen aus und geben die wahren Daten an den Writer weiter.

Beispiel Die nachfolgende Klasse wandelt Zeichen des Stroms in Kleinbuchstaben um. Sie arbeitet mit Hilfsfunktionen der Character-Klasse.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Listing 12.17 LowerCaseWriterDemo.java

import java.io.*;
class LowerCaseWriter extends FilterWriter
{
  public LowerCaseWriter( Writer writer )
  {
    super( writer );
  }
  public void write( int c ) throws IOException
  {
    out.write( Character.toLowerCase((char)c) );
  }
  public void
  write( char cbuf[], int off, int len ) throws IOException
  {
    out.write( String.valueOf(cbuf).toLowerCase(), off, len );
  }
  public void write( String s, int off, int len )
    throws IOException
  {
    out.write( s.toLowerCase(), off, len );
  }
}
public class LowerCaseWriterDemo
{
  public static void main( String args[] )
  {
    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() );
  }
}

Ein HTML-Writer

Unsere nächste Klasse bringt uns etwas näher an das HTML-Format heran. HTML steht für HyperText Markup Language. Wir wollen eine Klasse HTMLWriter entwerfen, die Filter Writer 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 <b>Dick</b>, so stellt er den Inhalt »Dick« in fetter Schrift dar, da das <B>-Tag 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.


Zeichen Zeichensequenz
< &lt;
> &gt;
& &amp;

Tabelle 12.4 HTML-Zeichen mit alternativen Zeichensequenzen

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. Absätze etwa werden mit <p> eingeleitet, einfache Zeilenvorschübe mit <br>. Unser HTMLWriter soll zwei leere Zeilen durch einen Absatz-Tag markieren. Demnach sollte unser Programm Leerzeilen zählen.

Alle sauberen HTML-Dateien haben einen wohl definierten Anfang und ein wohl definiertes Ende. Nachfolgendes kleine HTML-Dokument ist wohlgeformt und zeigt, was unser Programm für Einträge machen muss. Auf den DOCTYPE für korrektes XHTML wurde hier verzichtet.

<HTML>
<HEAD><TITLE>
Title of page
</TITLE></HEAD>
<BODY>
</BODY>
</HTML>

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

Listing 12.18 HTMLWriter.java

import java.io.*;
class HTMLWriter extends FilterWriter
{
  /**
   * New constructor with title of the page.
   */
  public HTMLWriter( Writer writer, String title )
  {
    super( writer );
    try {
      out.write( "<HTML><HEAD><TITLE>"
        + title
        + "</TITLE></HEAD><BODY>\n" );
    } catch ( IOException e ) { }
  }
  /**
   * Close the stream.
   */
  public void close() throws IOException
  {
    try {
      out.write( "</BODY></HTML>\n" );
    } catch ( IOException e ) { }
    out.close();
  }
  /**
   * Needed constructor without title on the page.
   */
  public HTMLWriter( Writer writer )
  {
    this( writer, "" );
  }
  /**
   * Write a single character.
   */
  public void write( int c ) throws IOException
  {
    switch ( c )
    {
      case '<' : out.write( "<" ); newLine=false; break;
      case '>' : out.write( ">" ); newLine=false; break;
      case '&' : out.write( "&" ); newLine=false; break;
      case '\n': if ( newLine ) {
                   out.write( "<P>\n" ); newLine=false;
                 }
                 else
                   out.write( "\n" );
                 newLine=true;
                 break;
      case '\r': break; // ignore
      default  : out.write( (int) c ); newLine=false;
    }
  }
  /**
   * Write a portion of an array of characters.
   */
  public void
  write( char cbuf[], int off, int len ) throws IOException
  {
    for ( int i=off; i<len; i++ )
      write( cbuf[i] );
  }
  /**
   * Write a portion of a string.
   */
  public void
  write( String s, int off, int len ) throws IOException
  {
    for ( int i=off; i<len; i++ )
      write( s.charAt( i ) );
  }
  private int lineNumberCnt;
  private boolean newLine;
}
public class HTMLWriterDemo
{
  public static void main( String args[] )
  {
    StringWriter sw = new StringWriter();
    HTMLWriter html = new HTMLWriter( sw, "Toll" );
    PrintWriter pw = new PrintWriter( html );
    pw.println( "Und eine Menge von Sonderzeichen: <, > und &" );
    pw.println( "Zweite Zeile" );
    pw.println( );
    pw.println( "Leerzeile" );
    pw.println( "Keine Leerzeile danach" );
    pw.close();
    System.out.println( sw.toString() );
  }
}

Im Demo-Programm erzeugen wir einen StringWriter, in dem wir die Daten ablegen. Wir müssen close() vor der Anweisung sw.toString() aufrufen, da wir andernfalls nicht den korrekten Abschluss sehen würden. Wenn wir nicht über den PrintWriter schließen würden, sondern über den HTMLWriter, dann müssten wir noch einen try/catch-Block um close() setzen, da sie eine IOException erzeugt. Nutzen wir aber PrintWriter, dann kümmert sich dieser darum, diese Exception zu fangen.

Unsere write()-Methoden sind sehr einfach, denn sie rufen für jedes Zeichen write(int) auf. Um das Programm hinreichend schnell zu machen, sollte also noch ein BufferedWriter um die Ausgaben gesetzt werden.

Die Ausgabe, die unser Programm nun erzeugt, ist Folgende:

<HTML><HEAD><TITLE>Toll</TITLE></HEAD><BODY>
Und eine Menge von Sonderzeichen: < und > und &
Zweite Zeile
<P>
Leerzeile
Keine Leerzeile danach
</BODY></HTML>




Copyright (c) Galileo Press GmbH 2004
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.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de