Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
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 Dateien berühren, neue Dateien anlegen
    gp 12.1.4 Umbenennen und Verzeichnisse anlegen
    gp 12.1.5 Die Wurzel aller Verzeichnisse/Laufwerke
    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.1.10 Sicherheitsprüfung
    gp 12.1.11 Namen der Laufwerke
    gp 12.1.12 Locking
  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 Stream-Klassen und Reader/Writer
    gp 12.3.1 Die abstrakten Basisklassen
    gp 12.3.2 Übersicht über Ein-/Ausgabeklassen
  gp 12.4 Binäre Ein-/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.5 Daten filtern durch FilterInputStream und FilterOutputStream
    gp 12.5.1 Der besondere Filter PrintStream
    gp 12.5.2 Die Schnittstelle Appendable
    gp 12.5.3 System.in und System.out
  gp 12.6 Besondere OutputStream- und InputStream-Klassen
    gp 12.6.1 Bytes in den Strom schreiben mit ByteArrayOutputStream
    gp 12.6.2 Bytes in den Strom schreiben mit ByteArrayInputStream
    gp 12.6.3 Ströme zusammensetzen mit SequenceInputStream
  gp 12.7 Ressourcen wie Grafiken aus dem Klassenpfad und aus Jar-Archiven laden
  gp 12.8 Die Unterklassen von Writer
    gp 12.8.1 Die abstrakte Klasse Writer
    gp 12.8.2 Datenkonvertierung durch den OutputStreamWriter
    gp 12.8.3 In Dateien schreiben mit der Klasse FileWriter
    gp 12.8.4 StringWriter und CharArrayWriter
    gp 12.8.5 Writer als Filter verketten
    gp 12.8.6 Gepufferte Ausgabe durch BufferedWriter
    gp 12.8.7 Ausgabemöglichkeiten durch PrintWriter erweitern
    gp 12.8.8 Daten mit FilterWriter filtern
  gp 12.9 Die Klassen um Reader
    gp 12.9.1 Die abstrakte Basisklasse Reader
    gp 12.9.2 Automatische Konvertierungen mit dem InputStreamReader
    gp 12.9.3 Dateien lesen mit der Klasse FileReader
    gp 12.9.4 StringReader und CharArrayReader
  gp 12.10 Die Filter für Zeichenströme
    gp 12.10.1 Gepufferte Eingaben mit der Klasse BufferedReader
    gp 12.10.2 LineNumberReader zählt automatisch Zeilen mit
    gp 12.10.3 Eingaben filtern mit der Klasse FilterReader
    gp 12.10.4 Daten mit der Klasse PushbackReader zurücklegen
  gp 12.11 Kommunikation zwischen Threads mit Pipes
    gp 12.11.1 PipedOutputStream und PipedInputStream
    gp 12.11.2 PipedWriter und PipedReader
  gp 12.12 Datenkompression
    gp 12.12.1 Die Java-Unterstützung beim Komprimieren und Zusammenpacken
    gp 12.12.2 Datenströme komprimieren
    gp 12.12.3 Zip-Archive
    gp 12.12.4 Jar-Archive
  gp 12.13 Prüfsummen
    gp 12.13.1 Die Schnittstelle Checksum
    gp 12.13.2 Die Klasse CRC32
    gp 12.13.3 Die Adler32-Klasse
  gp 12.14 Persistente Objekte und Serialisierung
    gp 12.14.1 Objekte speichern mit der Standard-Serialisierung
    gp 12.14.2 Objekte über die Standard-Serialisierung lesen
    gp 12.14.3 Die Schnittstelle Serializable
    gp 12.14.4 Nicht serialisierbare Attribute mit transient aussparen
    gp 12.14.5 Das Abspeichern selbst in die Hand nehmen
    gp 12.14.6 Tiefe Objektkopien
    gp 12.14.7 Versionenverwaltung und die SUID
    gp 12.14.8 Wie die ArrayList serialisiert
    gp 12.14.9 Serialisieren in XML-Dateien
    gp 12.14.10 XML-API von Sun
  gp 12.15 Zugriff auf SMB-Server
    gp 12.15.1 jCIFS
  gp 12.16 Tokenizer
    gp 12.16.1 StreamTokenizer
    gp 12.16.2 CSV (Comma Separated Values)-Dateien verarbeiten
  gp 12.17 Die Logging-API


Galileo Computing

12.8 Die Unterklassen von Writedowntop

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:


Tabelle 12.3   Übersicht der von Writer direkt 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

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.8.1 Die abstrakte Klasse Writer  downtop

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



abstract class java.io.  Writer  
implements Appendable, Closeable, Flushable

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  Writer append( char c ) throws IOException
Hängt ein Zeichen an. Verhält sich wie write(c), nur liefert es wie die Schnittstelle Appendable verlangt ein Appendable zurück. Writer ist ein passendes Appendable.
gp  Writer append( CharSequence csq ) throws IOException
Hängt ein Zeichenfoge an. Auch aus der Schnittstelle Appendable.
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. Aus der Schnittstelle Flushable.
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. Aus der Schnittstelle Closeable.

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 früher ein temporärer Puffer eingerichtet wurde. (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 – die ja in einer Unterklasse implementiert wird. 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.8.2 Datenkonvertierung durch den OutputStreamWriter  downtop

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.
gp  OutputStreamWriter( OutputStream out, Charset charset )
Erzeugt einen OutputStreamWriter mit einem Charset.
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.8.3 In Dateien schreiben mit der Klasse FileWriter  downtop

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, übergeben wir als zweites Argument true. 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.8.4 StringWriter und CharArrayWriter  downtop

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 StringWrite();  
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 ), StringWriter append( char c )
Schreibt ein einzelnes Zeichen.
gp  void write( String str ), StringWriter append( CharSequence seq )
Schreibt einen Zeichenfolge.
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 write( int c ), CharArrayWriter append( char c )
Schreibt ein Zeichen in den Puffer.
gp  CharArrayWriter append( CharSequence seq )
Schreibt ein Zeichenfolge.
gp  void write( char c[], int off, int len )
Schreibt ausgwählte 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.
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 close()
Schließt den Stream.
gp  void flush()
Leert den Stream.

Galileo Computing

12.8.5 Writer als Filter verketten  downtop

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
    {
      Writer fw = new FileWriter( "charArrayWriterDemoPuffer.txt" );
      Writer 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.8.6 Gepufferte Ausgabe durch BufferedWriter  downtop

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.

Alle write(), append()-Funktion sind so implementiert, dass sie erst im Puffer landen. Wenn der Puffer voll ist, werden wie an den im Konstruktor übergebenen Writer durchgespült.

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.8.7 Ausgabemöglichkeiten durch PrintWriter erweitern  downtop

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.

Die Konstruktoren erwarten entweder einen OutputStream oder einen Writer. Jeder Konstruktor existiert in zwei Varianten: ist der Wahrheitsparameter autoflush mit true belegt, wird nach einem Zeilenumbruch automatisch flush() aufgerufen.

In eine Datei ist dann einfach geschrieben:


Writer w = new FileWriter( "c:\datei.txt" );
PrintWriter out = new PrintWriter( w );
out.println( "Hallo Welt" );
out.close();


class java.io.  PrintWriter  
extends Writer

gp  PrintWriter( OutputStream out )
Erzeugt einen neuen PrintWriter aus einem OutputStream, der nicht automatisch am Zeilenende den Puffer schreibt.
gp  PrintWriter( Writer out )
Erzeugt einen neuen PrintWriter aus einem Writer, 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, boolean autoFlush )
Erzeugt einen neuen PrintWriter aus einem OutputStream, der automatisch am Zeilenende mittels println() den Puffer leert, falls autoFlush=true ist.
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), PrintWriter append(char c), PrintWriter append(CharSequence csq)
Schreibt Array von Zeichen, Zeichen 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.


Galileo Computing

12.8.8 Daten mit FilterWriter filtern  toptop

Die Architektur der Java-Klassen macht es leicht, eigene Filter zu programmieren. Basis ist dafür 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-Objektes 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 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.
gp  void close()
Schließt den Stream.
gp  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 dieses Ur-Writer.

Der Weg zum eigenen Filter

Drei Dinge sind für einen eigenen FilterWriter nötig:

gp  Unsere Klasse leitet sich von FilterWriter ab.
gp  Unser Konstruktor erwartet 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.


Tabelle 12.4   HTML-Zeichen mit alternativen Zeichensequenzen

Zeichen Zeichensequenz
< &lt;
> &gt;
& &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. 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( "&lt;" ); newLine = false; break;
      case>: out.write( "&gt;" ); newLine = false; break;
      case&: out.write( "&amp;" ); 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: &lt; und &gt; und &amp;
Zweite Zeile
<P>
Leerzeile
Keine Leerzeile danach
</BODY></HTML>




Copyright © 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