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.8 Schachteln von Eingabe-Streamsdowntop

Ebenso wie sich Datenströme in der Ausgabe schachteln lassen, können auch Eingabeströme hintereinander Daten verändern. Folgende Klassen stehen zur Verfügung, die im Konstruktor ein Reader erwarten: BufferedReader, LineNumberReader, FilterReader und PushbackReader. Der Reader wird intern unter der proteced-Variablen in verwaltet.


Galileo Computing

12.8.1 Gepufferte Eingaben mit der Klasse BufferedReaderdowntop

Ein BufferedReader puffert ähnlich wie ein BufferedWriter einige Daten vor. Die Daten werden also zuerst in einen kleinen Zwischenspeicher geladen, der wiederum wie beim BufferedWriter 8 KB groß ist. Durch die Bereitstellung der Daten müssen weniger Zugriffe auf den Datenträger vorgenommen werden, und die Geschwindigkeit der Anwendung erhöht sich. Aus BufferedReader geht direkt die Unterklasse LineNumberReader hervor, die Zeilennummern zugänglich macht. Da ein BufferedReader Markierungen und Sprünge erlaubt, werden die entsprechenden Funktionen von Reader überschrieben.

Die Klasse BufferedReader besitzt zwei Konstruktoren. Bei einem lässt sich die Größe des internen Puffers angeben.


class java.io.BufferedReader
extends Reader

gp BufferedReader( Reader in )
Erzeugt einen puffernden Zeichenstrom mit der Puffergröße von 8 KB.
gp BufferedReader( Reader in, int sz )
Erzeugt einen puffernden Zeichenstrom mit der Puffergröße sz.

Zusätzlich stellt BufferedReader die Methode readLine() zur Verfügung, die eine komplette Textzeile liest und als String an den Aufrufer zurückgibt.

gp String readLine()
Liest eine Zeile bis zum Zeilenende und gibt den String ohne die Endzeichen zurück. null, wenn der Stream am Ende ist.

Textzeilen lesen mit readLine() - früher und heute

Seit der Java-Version 1.1 ist die Methode readLine() aus der Klasse DataInputStream veraltet. Früher war Folgendes üblich, um eine Textzeile von der Konsole zu lesen:

DataInputStream in = new DataInputStream( System.in );
String s = in.readLine();

Heutzutage bietet die Klasse BufferedReader die Methode readLine() an. Die Programme, die den DataInputStream noch für die Zeileneingabe nutzen, sollten mit dem BufferedReader umgeschrieben werden. Somit ergibt sich eine Zeileneingabe von der Konsole nun mit folgenden Zeilen:

BufferedReader in = new BufferedReader( new InputStreamReader(System.in) );
String s = in.readLine();

Eine Textzeile ist durch die Zeichen »\n« oder »\r« begrenzt. Zusätzlich wird auch die Folge der beiden Zeichen beachtet, also »\r\n«. Die Methode basiert auf einer privaten Funktion der Klasse. Sie ruft readLine(boolean skipLF) auf, eine Methode, die auch für uns hin und wieder nützlich wäre. Sie bestimmt, ob die Zeilenendezeichen überlesen werden sollen oder nicht. Im Bedarfsfall bleibt uns nichts anderes übrig, als den Programmcode aus den Originalquellen zu kopieren.


Beispiel Das nachfolgende Programm implementiert ein einfaches »cat«-Kommando von Unix. Es können auf der Parameterzeile Dateinamen übergeben werden. Die Dateien werden dann in der Standardausgabe ausgegeben.

Listing 12.21 cat.java

import java.io.*;
class cat
{
  public static void main( String args[] )
  {
    try
    {
      for ( int i = 0; i < args.length; i++ )
      {
        BufferedReader in = new BufferedReader( new FileReader(args[i]) );
        
        for ( String line; (line = in.readLine()) != null; )
          System.out.println( line );
          
        in.close();
      }
    }
    catch ( IOException e ) {
      System.err.println( e );
    }
  }
}

Galileo Computing

12.8.2 LineNumberReader zählt automatisch Zeilen mitdowntop

LineNumberReader ist die einzige Klasse, die als Unterklasse von BufferedReader aus den Java-Bibliotheken hervorgeht. Ein LineNumberReader liest die Eingabezeilen und zählt gleichzeitig die Zeilen, die gelesen wurden. Mit zwei Funktionen lässt sich auf die Zeilennummern zugreifen: getLineNumber() und setLineNumber(). Dass die Zeilennummer auch geschrieben werden kann, ist sicherlich ungewöhnlich, aber intern wird nur die Variable lineNumber geschrieben. Bei getLineNumber() wird diese Variable zurückgeliefert. Bei jedem read() untersuchen die Funktionen, ob im Eingabestrom ein »\n« oder »\r« vorkommt. Wenn dies der Fall ist, dann inkrementieren sie die Variable lineNumber.

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


class java.io.LineNumberReader
extends BufferedReader

gp int getLineNumber()
Liefert die aktuelle Zeilennummer.
gp void setLineNumber( int lineNumber )
Setzt die aktuelle Zeilennummer.

Beispiel Die nachfolgende Klasse verbindet einen LineNumberReader mit einer Datei aus einem FileReader. Dann lesen wir die Zeilen mittels readLine() aus. Nun ist es praktisch, dass LineNumberReader eine Erweiterung von BufferedReader ist, die uns diese praktische Funktion gibt. Wir geben zunächst die Zeilennummer und dann die Zeile selbst aus.

Listing 12.22 LineNumberReaderDemo.java

import java.io.*;
public class LineNumberReaderDemo
{
  public static void main( String args[] )
  {
    try
    {
      Reader fr = new FileReader( "LineNumberReaderDemo.java" );
      LineNumberReader f = new LineNumberReader( fr );

      for ( String line; (line = f.readLine()) != null; )
        System.out.println( f.getLineNumber() + ": " + line );
      f.close();
    }
    catch ( IOException e ) {
      System.out.println( "Fehler beim Lesen der Datei" );
    }
  }
}

Galileo Computing

12.8.3 Eingaben filtern mit der Klasse FilterReaderdowntop

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 den Parameter in der protected-Variablen in. Der Konstruktor ist protected, da auch er von der Unterklasse mit super() aufgerufen werden soll. Dazu lässt sich das Beispiel aus dem FilterWriter noch einmal heranziehen. Alle Aufrufe, die an den FilterReader gehen, werden an den Reader in weitergeleitet, das heißt etwa, wenn der FilterReader geschlossen wird, dann wird der Aufruf in.close() ausgeführt. Aus diesem Grunde muss der FilterReader auch alle Methoden von Reader überschreiben, da ja eine Umleitung stattfindet.

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


abstract class java.io.FilterReader
extends Reader

gp protected Reader in
Der Zeicheneingabestrom oder null, wenn der Strom geschlossen wurde.
gp protected FilterReader( Reader in )
Erzeugt einen neuen filternden Reader.

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. Wenn dieser eine Exception wirft, wird sie an uns weitergeleitet.

HTML-Tags mit einem speziellen Filter überlesen

Unser nächstes Beispiel ist eine Klasse, die den FilterReader so erweitert, dass HTML-Tags überlesen werden. Sie werden allerdings nicht so komfortabel wie beim HTMLWriter im Datenstrom umgesetzt. Die Klasse überschreibt den notwendigen Konstruktor und implementiert die beiden read()-Methoden. Die read()-Methode ohne Parameter legt einfach ein ein Zeichen großes Feld an und ruft dann die read()-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 Reader in zu lesen, sondern hier müssen wir beachten, dass eingestreute Tags nicht zählen. Die Zeichenkette <p>Hallo<p> ist demnach fünf Zeichen lang und nicht elf. Liegt eine solche Zeichenkette vor, so müssen mehr als vier Zeichen vom darunter liegenden Reader abgenommen werden.

Listing 12.23 HTMLReader.java

import java.io.*;
class HTMLReader extends FilterReader
{
  public HTMLReader( Reader in )
  {
    super( in );
  }
  public int read() throws IOException
  {
    char buf[] = new char[1];
    return read(buf, 0, 1) == -1 ? -1 : buf[0];
  }
  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;
  }
  private boolean intag = false;
}
public class HTMLReaderDemo
{
  public static void main( String args[] )
  {
    try {
      String s = "<html>Hallo! <b>Ganz schön fett.</b>"+
                 "Ah, wieder normal.</html>";
      StringReader sr = new StringReader( s );
      HTMLReader hr = new HTMLReader( sr );
      BufferedReader in = new BufferedReader( hr );
      String t;
      while ( (t = in.readLine()) != null )
        System.out.println( t );
      in.close();
    }
    catch( Exception e ) { System.err.println( e ); }
  }
}

Das Programm produziert dann die einfache Ausgabe:

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

Der einzige Grund, warum wir auf den HTMLReader noch einen BufferedReader aufsetzen, ist der, dass wir dann die readLine()-Methode nutzen können.


Galileo Computing

12.8.4 Daten mit der Klasse PushbackReader zurücklegentoptop

Der Eingabefilter PushbackReader ist die einzige Klasse, die direkt aus FilterReader abgeleitet ist. Sie definiert eine Filter-Klasse, die einen Puffer einer beliebigen Größe besitzt, in den Zeichen wieder zurückgeschrieben werden können.

Schreiben wir einen Parser, der eine Wahl aufgrund des nächsten gelesenen Zeichens (ein so genannter Vorausschauender Parser) trifft, dann kann er dieses Zeichen wieder in den Eingabestrom legen, wenn er den Weg doch nicht verfolgen möchte. Hier ist der Einsatz der Klasse PushbackReader angebracht. Denn der nächste Lesezugriff liest dann dieses zurückgeschriebene Zeichen.

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


class java.io.PushbackReader
extends FilterReader

gp PushbackReader( Reader in )
Erzeugt einen PushbackReader aus dem Reader in mit der Puffergröße 1.
gp 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.

gp 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.

Zeilennummern entfernen

Das nächste Programm demonstriert die Möglichkeiten eines PushbackReaders. Die Implementierung wirkt möglicherweise etwas gezwungen, sie zeigt jedoch, wie unread() 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 schon ein Zeichen mehr gelesen wurde. In einem normalen Programm ohne die Option, das Zeichen zurücklegen zu können, würde das etwas 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() in den Datenstrom, und dann kann der nachfolgende Programmcode einfach so weitermachen, als ob nichts gewesen wäre. Dies ist besonders dann von Vorteil, wenn noch Unterprogramme im Einsatz sind, die nach dem Lesen der Zahl eine weitere Funktion aufrufen, die noch einmal alles lesen will. Nach der herkömmlichen Methode muss das gelesene Zeichen dann mit an die Funktion übergeben werden.

Listing 12.24 PushbackReaderDemo.java

import java.io.*;
class PushbackReaderDemo
{
  public static void main( String args[] ) throws IOException
  {
    String s = "134Erste Zeile\n234Zeile";
  
    PushbackReader in = new PushbackReader(new StringReader(s));
    boolean eof = false;
    int c;
    while ( !eof )
    {
      try
      {
        int number = 0;
        // Lese Zahl bis nichts mehr geht
        while ( Character.isDigit((char)(c = in.read())) )
          number = (number * 10) + (c-'0');
      
        if ( c == -1 )   // Ende der Datei => Ende der Schleife
        {
          eof = true;
          continue;
        }
        else
          in.unread( c );        // Letztes Zeichen wieder rein
        System.out.print( number + ":" );
      
        // Hier ist das Zeichen wieder drinne
      
        while ( (c = in.read()) != -1 )
        {
          System.out.print( (char)c );
          if ( c == '\n' )
            break;
        }
        if ( c == -1 ) {                 // Ende der Schleife
          eof = true;
          continue;
        }
      }
      catch ( EOFException e )
      {
        eof = true;
      }
    }
  }
}

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 »\n\r« gelesen werden. So wie die Methode von uns jetzt programmiert ist, ist sie auf Unix-Plattformen eingeschränkt, die nur ein einziges Ende-Zeichen 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() nicht durchgeführt, und das erste Zeichen nach der Zahl fehlt.





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