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

Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Sprachbeschreibung
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Mathematisches
6 Eigene Klassen schreiben
7 Angewandte Objektorientierung
8 Exceptions
9 Generics, innere Klassen
10 Die Klassenbibliothek
11 Threads und nebenläufige Programmierung
12 Datenstrukturen und Algorithmen
13 Raum und Zeit
14 Dateien und Datenströme
15 Die eXtensible Markup Language (XML)
16 Grafische Oberflächen mit Swing
17 Grafikprogrammierung
18 Netzwerkprogrammierung
19 Verteilte Programmierung mit RMI und Web–Services
20 JavaServer Pages und Servlets
21 Applets
22 Midlets und die Java ME
23 Datenbankmanagement mit JDBC
24 Reflection und Annotationen
25 Logging und Monitoring
26 Sicherheitskonzepte
27 Java Native Interface (JNI)
28 Dienstprogramme für die Java-Umgebung
Stichwort

Download:
- ZIP, ca. 14,1 MB
Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java ist auch eine Insel (8. Auflage) von Christian Ullenboom
Programmieren mit der Java Standard Edition Version 6
Buch: Java ist auch eine Insel (8. Auflage)

Java ist auch eine Insel (8. Aufl.)
8., aktual. Auflage, geb., mit DVD
1.475 S., 49,90 Euro
Galileo Computing
ISBN 978-3-8362-1371-4
Pfeil 14 Dateien und Datenströme
Pfeil 14.1 Datei und Verzeichnis
Pfeil 14.1.1 Dateien und Verzeichnisse mit der Klasse File
Pfeil 14.1.2 Verzeichnis oder Datei? Existiert es?
Pfeil 14.1.3 Verzeichnis- und Dateieigenschaften/-attribute
Pfeil 14.1.4 Wurzelverzeichnis, Laufwerksnamen, Plattenspeicher
Pfeil 14.1.5 Umbenennen und Verzeichnisse anlegen
Pfeil 14.1.6 Verzeichnisse listen und Dateien filtern
Pfeil 14.1.7 Dateien berühren, neue Dateien anlegen, temporäre Dateien
Pfeil 14.1.8 Dateien und Verzeichnisse löschen
Pfeil 14.1.9 Verzeichnisse nach Dateien iterativ durchsuchen
Pfeil 14.1.10 URL- und URI-Objekte aus einem File-Objekt ableiten
Pfeil 14.1.11 Mit Locking Dateien sperren
Pfeil 14.1.12 Sicherheitsprüfung
Pfeil 14.2 Dateien mit wahlfreiem Zugriff
Pfeil 14.2.1 Ein RandomAccessFile zum Lesen und Schreiben öffnen
Pfeil 14.2.2 Aus dem RandomAccessFile lesen
Pfeil 14.2.3 Schreiben mit RandomAccessFile
Pfeil 14.2.4 Die Länge des RandomAccessFile
Pfeil 14.2.5 Hin und her in der Datei
Pfeil 14.3 Stream-Klassen und Reader/Writer am Beispiel von Dateien
Pfeil 14.3.1 Mit dem FileWriter Texte in Dateien schreiben
Pfeil 14.3.2 Zeichen mit der Klasse FileReader lesen
Pfeil 14.3.3 Kopieren mit FileOutputStream und FileInputStream
Pfeil 14.3.4 Das FileDescriptor-Objekt
Pfeil 14.4 Basisklassen für die Ein-/Ausgabe
Pfeil 14.4.1 Die abstrakten Basisklassen
Pfeil 14.4.2 Übersicht über Ein-/Ausgabeklassen
Pfeil 14.4.3 Die abstrakte Basisklasse OutputStream
Pfeil 14.4.4 Die Schnittstellen Closeable und Flushable
Pfeil 14.4.5 Ein Datenschlucker
Pfeil 14.4.6 Die abstrakte Basisklasse InputStream
Pfeil 14.4.7 Ressourcen aus dem Klassenpfad und aus Jar–Archiven laden
Pfeil 14.4.8 Ströme mit SequenceInputStream zusammensetzen
Pfeil 14.4.9 Die abstrakte Basisklasse Writer
Pfeil 14.4.10 Die Schnittstelle Appendable
Pfeil 14.4.11 Die abstrakte Basisklasse Reader
Pfeil 14.5 Formatierte Textausgaben
Pfeil 14.5.1 Die Klassen PrintWriter und PrintStream
Pfeil 14.5.2 System.out, System.err und System.in
Pfeil 14.5.3 Geschützte Passwort-Eingaben mit der Klasse Console
Pfeil 14.6 Schreiben und Lesen aus Strings und Byte-Feldern
Pfeil 14.6.1 Mit dem StringWriter ein String-Objekt füllen
Pfeil 14.6.2 CharArrayWriter
Pfeil 14.6.3 StringReader und CharArrayReader
Pfeil 14.6.4 Mit ByteArrayOutputStream in ein Byte-Feld schreiben
Pfeil 14.6.5 Mit ByteArrayInputStream aus einem Byte-Feld lesen
Pfeil 14.7 Datenströme filtern und verketten
Pfeil 14.7.1 Streams als Filter verketten
Pfeil 14.7.2 Gepufferte Ausgaben mit BufferedWriter/BufferedOutputStream
Pfeil 14.7.3 Gepufferte Eingaben mit BufferedReader/BufferedInputStream
Pfeil 14.7.4 LineNumberReader zählt automatisch Zeilen mit
Pfeil 14.7.5 Daten mit der Klasse PushbackReader zurücklegen
Pfeil 14.7.6 DataOutputStream/DataInputStream
Pfeil 14.7.7 Basisklassen für Filter
Pfeil 14.7.8 Die Basisklasse FilterWriter
Pfeil 14.7.9 Ein LowerCaseWriter
Pfeil 14.7.10 Eingaben mit der Klasse FilterReader filtern
Pfeil 14.8 Vermittler zwischen Byte-Streams und Unicode-Strömen
Pfeil 14.8.1 Datenkonvertierung durch den OutputStreamWriter
Pfeil 14.8.2 Automatische Konvertierungen mit dem InputStreamReader
Pfeil 14.9 Kommunikation zwischen Threads mit Pipes
Pfeil 14.9.1 PipedOutputStream und PipedInputStream
Pfeil 14.9.2 PipedWriter und PipedReader
Pfeil 14.10 Datenkompression
Pfeil 14.10.1 Java-Unterstützung beim Komprimieren
Pfeil 14.10.2 Datenströme komprimieren
Pfeil 14.10.3 Zip-Archive
Pfeil 14.10.4 Jar-Archive
Pfeil 14.11 Prüfsummen
Pfeil 14.11.1 Die Schnittstelle Checksum
Pfeil 14.11.2 Die Klasse CRC32
Pfeil 14.11.3 Die Adler32-Klasse
Pfeil 14.12 Persistente Objekte und Serialisierung
Pfeil 14.12.1 Objekte mit der Standard-Serialisierung speichern und lesen
Pfeil 14.12.2 Zwei einfache Anwendungen der Serialisierung
Pfeil 14.12.3 Die Schnittstelle Serializable
Pfeil 14.12.4 Nicht serialisierbare Attribute aussparen
Pfeil 14.12.5 Das Abspeichern selbst in die Hand nehmen
Pfeil 14.12.6 Tiefe Objektkopien
Pfeil 14.12.7 Versionenverwaltung und die SUID
Pfeil 14.12.8 Wie die ArrayList serialisiert
Pfeil 14.12.9 Probleme mit der Serialisierung
Pfeil 14.12.10 Serialisieren in XML-Dateien
Pfeil 14.12.11 JavaBeans Persistence
Pfeil 14.12.12 XStream
Pfeil 14.13 Tokenizer
Pfeil 14.13.1 StreamTokenizer
Pfeil 14.13.2 CSV-Dateien verarbeiten
Pfeil 14.14 Zum Weiterlesen


Galileo Computing - Zum Seitenanfang

14.4 Basisklassen für die Ein-/Ausgabe Zur nächsten ÜberschriftZur vorigen Überschrift


Galileo Computing - Zum Seitenanfang

14.4.1 Die abstrakten Basisklassen Zur nächsten ÜberschriftZur vorigen Überschrift

Die konkreten Eingabe-/Ausgabe-Klassen wie FileInputStream, FileOutputStream, FileWriter oder BufferedWriter erweitern abstrakte Oberklassen. Im Allgemeinen können wir vier Kategorien bilden: Klassen zur Ein-/Ausgabe von Bytes (oder Byte-Arrays) und Klassen zur Ein-/Ausgabe von Unicode-Zeichen (Arrays oder Strings).


Basisklasse für Bytes (oder Byte-Arrays) Zeichen (oder Zeichen-Arrays)

Eingabe

InputStream

Reader

Ausgabe

OutputStream

Writer


Die Klassen InputStream und OutputStream bilden die Basisklassen für alle byte-orientierten Klassen und dienen somit als Bindeglied bei Methoden, die als Parameter ein Eingabe- und Ausgabe-Objekt verlangen. So ist ein InputStream nicht nur für Dateien denkbar, sondern auch für Daten, die über das Netzwerk kommen. Das Gleiche gilt für Reader und Writer; sie sind die abstrakten Basisklassen zum Lesen und Schreiben von Unicode-Zeichen und Unicode-Zeichenfolgen. Die Basisklassen geben abstrakte read()- oder write()-Methoden vor, die Unterklassen überschreiben, da nur sie wissen, wie etwas tatsächlich gelesen oder geschrieben wird.


Galileo Computing - Zum Seitenanfang

14.4.2 Übersicht über Ein-/Ausgabeklassen Zur nächsten ÜberschriftZur vorigen Überschrift

Während die abstrakten Basisklassen von keiner konkreten Datenquelle lesen oder in keine Ressource schreiben, implementieren die Unterklassen eine ganz bestimmte Strategie für eine Ressource. So weiß zum Beispiel ein FileWriter, wie für Dateien die abstrakte Klasse Writer zu implementieren ist, also wie Unicode-Zeichen in eine Datei geschrieben werden.

Die folgenden Tabellen vermitteln einen Überblick über die wichtigsten Unterklassen von InputStream/OutputStream und Reader/Writer. Die erste Tabelle listet die Eingabeklassen auf – und stellt die byte-orientierten und zeichenorientierten Klassen gegenüber –, und die zweite Tabelle zeigt die wesentlichen Ausgabeklassen.


Tabelle 14.2 Wichtige Eingabeklassen

Byte-Stream-Klasse für die Eingabe Zeichen-Stream-Klasse für die Eingabe Beschreibung

InputStream

Reader

Abstrakte Klasse für Zeicheneingabe und Byte-Arrays

BufferedInputStream

BufferedReader

Puffert die Eingabe

LineNumberInputStream

LineNumberReader

Merkt sich Zeilennummern beim Lesen

ByteArrayInputStream

CharArrayReader

Liest Zeichen-Arrays oder Byte-Arrays

(keine Entsprechung)

InputStreamReader

Wandelt einen Byte-Stream in einen Zeichen-Stream um. Sie ist das Bindeglied zwischen Byte und Zeichen

DataInputStream

(keine Entsprechung)

Liest Primitive und auch UTF-8

FilterInputStream

FilterReader

Abstrakte Klasse für gefilterte Eingabe

PushbackInputStream

PushbackReader

Erlaubt, gelesene Zeichen wieder in den Stream zu geben

PipedInputStream

PipedReader

Liest von einem PipedWriter oder PipedOutputStream

StringBufferInputStream (unter 1.1 nicht mehr erwünscht)

StringReader

Liest aus Strings

SequenceInputStream

(keine Entsprechung)

Verbindet mehrere InputStreams

TelepathicInputStream

TelepathicWriter

Überträgt Daten mittels Telepathie [Noch in der Entwicklung.]



Tabelle 14.3 Wichtige Ausgabeklassen

Byte-Stream-Klasse für die Ausgabe Zeichen-Stream-Klasse für die Ausgabe Beschreibung

OutputStream

Writer

Abstrakte Klasse für Zeichenausgabe oder Byte-Ausgabe

BufferedOutputStream

BufferedWriter

Ausgabe des Puffers, nutzt passendes Zeilenendezeichen

ByteArrayOutputStream

CharArrayWriter

Schreibt in Arrays

DataOutputStream

(keine Entsprechung)

Schreibt Primitive und auch UTF-8

(keine Entsprechung)

OutputStreamWriter

Übersetzt Zeichen-Streams in Byte-Streams

FileOutputStream

FileWriter

Schreibt in eine Datei

PrintStream

PrintWriter

Konvertiert primitive Datentypen in Strings und schreibt sie in einen Ausgabestrom

PipedOutputStream

PipedWriter

Schreibt in eine Pipe

(keine Entsprechung)

StringWriter

Schreibt in einen String



Galileo Computing - Zum Seitenanfang

14.4.3 Die abstrakte Basisklasse OutputStream Zur nächsten ÜberschriftZur vorigen Überschrift

Der Clou bei allen Datenströmen ist nun, dass spezielle Unterklassen wissen, wie sie genau die vorgeschriebene Funktionalität implementieren. Wenn wir uns den OutputStream anschauen, dann sehen wir auf den ersten Blick, dass hier alle wesentlichen Operationen um das Schreiben versammelt sind. Das heißt, dass ein konkreter Stream, der in Dateien schreibt, nun weiß, wie er Bytes in Dateien schreiben wird. Java ist auf der unteren Ebene mit seiner Plattform-unabhängigkeit am Ende, und native Methoden schreiben die Bytes.


abstract class java.io.OutputStream 
implements Closeable, Flushable

  • abstract void write( int b ) throws IOException
    Schreibt ein einzelnes Byte in den Datenstrom.
  • void write( byte[] b ) throws IOException
    Schreibt die Bytes aus dem Array in den Strom.
  • void write( byte[] b, int off, int len ) throws IOException
    Schreibt Teile des Byte-Feldes, nämlich len Byte ab der Position off, in den Ausgabestrom.
  • void close() throws IOException
    Schließt den Datenstrom. Einzige Methode aus Closeable.
  • void flush() throws IOException
    Schreibt noch im Puffer gehaltene Daten. Einzige Methode aus der Schnittstelle Flushable.

Die IOException ist keine RuntimeException, muss also behandelt werden.

Zwei Eigenschaften lassen sich an den Methoden ablesen: zum einen, dass nur Bytes geschrieben werden, und zum anderen, dass nicht wirklich alle Methoden abstract sind. Nicht alle diese Methoden sind wirklich elementar, müssen also nicht von allen Ausgabeströmen überschrieben werden. Wir entdecken, dass nur write(int) abstrakt ist. Das würde aber bedeuten, dass alle anderen Methoden konkret wären. Gleichzeitig stellt sich die Frage, wie ein OutputStream, der die Eigenschaften für alle erdenklichen Ausgabeströme vorschreibt, denn wissen kann, wie ein spezieller Ausgabestrom etwa geschlossen (close()) wird oder seine gepufferten Bytes schreibt (flush()). Das weiß er natürlich nicht, aber die Entwickler haben sich dazu entschlossen, eine leere Implementierung anzugeben. Der Vorteil besteht darin, dass Programmierer von Unterklassen nicht verpflichtet werden, immer die Methoden zu überschreiben, auch wenn sie sie gar nicht nutzen wollen.

Über konkrete und abstrakte Schreibmethoden

Es fällt auf, dass es zwar drei Schreibmethoden gibt, aber nur eine davon wirklich abstrakt ist. Das ist trickreich, denn tatsächlich lassen sich die Methoden, die ein Byte-Feld schreiben, auf die Methode abbilden, die ein einzelnes Byte schreibt. Wir werfen einen Blick in den Quellcode der Bibliothek:

public void write(byte[] b) throws IOException { 
  write(b, 0, b.length); 
} 
 
public void write(byte[] b, int off, int len) throws IOException { 
  if (b == null) 
    throw new NullPointerException(); 
  else if ((off < 0) || (off > b.length) || (len < 0) || 
    ((off + len) > b.length) || ((off + len) < 0)) { 
    throw new IndexOutOfBoundsException(); 
  } else if (len == 0) 
    return; 
  for (int i = 0 ; i < len ; i++) 
    write(b[off + i]); 
}

An beiden Implementierungen ist zu erkennen, dass sie die Arbeit sehr bequem an andere Methoden verschieben. Doch diese Implementierung ist nicht optimal! Stellen wir uns vor, ein Dateiausgabestrom überschreibt nur die eine abstrakte Methode, die nötig ist. Und nehmen wir weiterhin an, dass unser Programm nun immer ganze Byte-Felder schreibt, etwa eine 5-MB-Datei, die im Speicher steht. Dann werden für jedes Byte im Byte-Array in einer Schleife alle Bytes der Reihe nach an eine vermutlich native Methode übergeben. Wenn es so implementiert wäre, könnten wir die Geschwindigkeit des Mediums überhaupt nicht nutzen, zumal jedes Dateisystem Funktionen bereitstellt, mit denen sich ganze Blöcke übertragen lassen. Glücklicherweise sieht die Implementierung nicht so aus, da wir in dem Modell vergessen haben, dass die Unterklasse zwar die abstrakte Methode implementieren muss, aber immer noch andere Methoden überschreiben kann. Ein späterer Blick auf die Klasse FileOutputStream bestätigt dies.


Hinweis Ruft eine Oberklasse eine abstrakte Methode auf, die in der Unterklasse implementiert wird, ist das ein Entwurfsmuster mit dem Namen Schablonen-Muster oder englisch »template pattern«.



Galileo Computing - Zum Seitenanfang

14.4.4 Die Schnittstellen Closeable und Flushable Zur nächsten ÜberschriftZur vorigen Überschrift

Closeable wird von allen lesenden und schreibenden Datenstrom-Klassen implementiert, die geschlossen werden können. Das sind alle Reader/Writer- und InputStream/OutputStream-Klassen.


class java.io.Closeable

  • void close() throws IOException
    Schließt den Datenstrom. Einen geschlossenen Strom noch einmal zu schließen, hat keine Konsequenz.

Flushable findet sich nur bei schreibenden Klassen und ist insbesondere bei denen wichtig, die Daten puffern.


class java.io.Flushable

  • void flush() throws IOException
    Schreibt gepufferte Daten in den Strom.

Die Basisklassen Reader und OutputStream implementieren diese Schnittstelle, aber auch Formatter.


Galileo Computing - Zum Seitenanfang

14.4.5 Ein Datenschlucker Zur nächsten ÜberschriftZur vorigen Überschrift

Damit wir sehen können, wie alle Unterklassen prinzipiell mit OutputStream umgehen, wollen wir eine Klasse entwerfen, die alle ihr gesendeten Daten verwirft. Die Klasse ist mit dem Unix-Device /dev/null vergleichbar. Die Implementierung ist die einfachste, die sich denken lässt, denn alle write()-Methoden machen nichts.

Listing 14.18 com/tutego/insel/io/stream/NullOutputStream.java

package com.tutego.insel.io.stream; 
 
public final class NullOutputStream extends java.io.OutputStream 
{ 
  @Override public void write( byte[] b ) { /* Empty */ } 
  @Override public void write( byte[] b, int off, int len ) { /* Empty */ } 
  @Override public void write( int b ) { /* Empty */ } 
}

Da close() und flush() ohnehin schon mit einem leeren Block implementiert sind, brauchen wir sie nicht noch einmal zu überschreiben. Aus Effizienzgründen (!) geben wir auch eine Implementierung für die Schreib-Feld-Methoden an.


Galileo Computing - Zum Seitenanfang

14.4.6 Die abstrakte Basisklasse InputStream Zur nächsten ÜberschriftZur vorigen Überschrift

Das Gegenstück zu OutputStream ist InputStream; jeder binäre Eingabestrom wird durch die abstrakte Klasse InputStream repräsentiert. Die Konsoleneingabe System.in ist vom Typ InputStream.


abstract class java.io.InputStream 
implements Closeable

  • int available() throws IOException
    Gibt die Anzahl der verfügbaren Zeichen im Datenstrom zurück, die sofort ohne Blockierung gelesen werden können.
  • int read() throws IOException
    Liest ein Byte als Integer aus dem Datenstrom. Ist das Ende des Datenstroms erreicht, wird –1 übergeben. Die Methode ist überladen, wie die nächsten Signaturen zeigen.
  • int read( byte[] b ) throws IOException
    Liest mehrere Bytes in ein Feld. Die tatsächliche Länge der gelesenen Bytes wird zurückgegeben und muss nicht b.length() sein.
  • int read( byte[] b, int off, int len ) throws IOException
    Liest den Datenstrom in ein Byte-Feld, schreibt ihn aber erst an der Stelle off in das Byte-Feld. Zudem begrenzt len die maximale Anzahl der zu lesenden Zeichen.
  • long skip( long n ) throws IOException Überspringt eine Anzahl von Zeichen. Die Rückgabe gibt die tatsächlich gesprungenen Bytes zurück, was nicht mit n identisch sein muss.
  • void close() throws IOException
    Schließt den Datenstrom. Operation aus der Schnittstelle Closeable.
  • boolean markSupported()
    Gibt einen Wahrheitswert zurück, ob der Datenstrom das Merken und Zurücksetzen von Positionen gestattet. Diese Markierung ist ein Zeiger, der auf bestimmte Stellen in der Eingabedatei zeigen kann.
  • void mark( int readlimit )
    Merkt sich eine Position im Datenstrom.
  • void reset() throws IOException
    Springt wieder zu der Position zurück, die mit mark() gesetzt wurde.

Auffällig ist, dass bis auf mark() und markSupported() alle Methoden im Fehlerfall eine IOException auslösen.


Hinweis available() liefert die Anzahl Bytes, die ohne Blockierung gelesen werden können. (Blockieren bedeutet, dass die Methode nicht sofort zurückkehrt, sondern erst wartet, bis neue Daten vorhanden sind.) Die Rückgabe von available() sagt nichts darüber aus, wie viele Zeichen der InputStream insgesamt hergibt. Während aber bei FileInputStream die Methode available() üblicherweise doch die Dateilänge liefert, ist dies bei den Netzwerk-Streams im Allgemeinen nicht der Fall.



Galileo Computing - Zum Seitenanfang

14.4.7 Ressourcen aus dem Klassenpfad und aus Jar–Archiven laden Zur nächsten ÜberschriftZur vorigen Überschrift

Um Ressourcen wie Grafiken oder Konfigurationsdateien aus Jar-Archiven zu laden, ist die Methode getResourceAsStream() beziehungsweise getResource() ideal. Beide sind Methoden des Class-Objekts. getResource() gibt ein URL-Objekt für die Ressource zurück. Da oft der Inhalt des Datenstroms interessant ist, liefert getResourceAsStream() einen InputStream. Intern wird aber nichts anderes gemacht, als getResource() aufzurufen und mit openStream() ein Eingabe-Objekt zu holen. Nur getResourceAsStream() fängt eine eventuelle IOException ab und liefert dann die Rückgabe null.

Da der Klassenlader die Ressource findet, entdeckt er alle Dateien, die im Pfad des Klassenladers eingetragen sind. Das gilt auch für Jar-Archive, weil dort vom Klassenlader alles verfügbar ist. Konnte die Quelle nicht aufgelöst werden, liefern die Methoden null. Die Methode getResourceAsStream() liefert auch null, wenn die Sicherheitsrichtlinien das Lesen verbieten.


Beispiel Besorge einen Eingabestrom in1 auf die Datei kullin_fun.txt und einen zweiten Eingabestrom in2 auf die Datei hirse_fun.jpg innerhalb der eigenen Methode init():

class Classi 
{ 
  InputStream in1 = Classi.class.getResourceAsStream( "kullin_fun.txt" ); 
  void init() 
  { 
    InputStream in2 = getClass().getResourceAsStream( "hirse_fun.jpg" ); 
  } 
}

Da zum Nutzen der getResourceXXX()-Methoden ein Class-Objekt nötig ist, zeigt das Beispiel zum einen, dass über Classi.class das Class-Objekt zu bekommen ist, und zum anderen, dass in einer Objektmethode ebenfalls die geerbte Object-Methode getClass() ein Class-Objekt liefert.


Galileo Computing - Zum Seitenanfang

14.4.8 Ströme mit SequenceInputStream zusammensetzen Zur nächsten ÜberschriftZur vorigen Überschrift

Ein SequenceInputStream-Filter hängt mehrere Eingabeströme zu einem großen Eingabestrom zusammen. Nützlich ist dies, wenn wir aus Strömen lesen wollen und es uns egal ist, was für ein Strom es ist, wo er startet und wo er aufhört. Der SequenceInputStream lässt sich erzeugen, indem im Konstruktor zwei InputStream-Objekte mitgegeben werden. Soll aus zwei Dateien ein zusammengesetzter Datenstrom gebildet werden, benutzen wir folgende Programmzeilen:

InputStream s1 = new FileInputStream( "teil1.txt" ); 
InputStream s2 = new FileInputStream( "teil2.txt" ); 
InputStream s  = new SequenceInputStream( s1, s2 );

Ein Aufruf irgendeiner read()-Methode liest nun erst Daten aus s1. Liefert s1 keine Daten mehr, kommen die Daten aus s2. Liegen keine Daten mehr an s2, aber wieder an s1, ist es zu spät.

Für drei Ströme kann eine Kette aus zwei SequenceInputStream-Objekten gebaut werden:

InputStream in = new SequenceInputStream( stream1, 
  new SequenceInputStream(stream2, stream3) );

Sollen mehr als zwei Ströme miteinander verbunden werden, kann auch eine Enumeration im Konstruktor übergeben werden. Die Enumeration einer Datenstruktur gibt dann die zu kombinierenden Datenströme zurück. Wir haben eine Datenstruktur, die sich hier gut anbietet: Vector. (List geht leider nicht, da diese nur einen Iterator, aber keine Enumeration liefert.)

Vector<InputStream> v = new Vector<InputStream>(); 
v.addElement( stream1 ); 
v.addElement( stream2 ); 
v.addElement( stream3 ); 
InputStream seq = new SequenceInputStream( v.elements() );

Wir verstauen alle Eingabeströme in einen Vector und nutzen dann die elements()-Methode für die Aufzählung.


class java.io.SequenceInputStream 
extends InputStream

  • SequenceInputStream( InputStream s1, InputStream s2 )
    Erzeugt einen SequenceInputStream aus zwei einzelnen InputStream-Objekten. Zuerst werden die Daten aus s1 gelesen und dann aus s2.
  • SequenceInputStream( Enumeration<? extends InputStream> e )
    Die Eingabeströme für den SequenceInputStream werden aus der Enumeration mit nextElement() geholt. Ist ein Strom ausgesaugt, wird die close()-Methode aufgerufen und der nächste vorhandene Strom geöffnet.
  • int available() throws IOException
    Liefert die Anzahl der Zeichen, die gelesen werden können. Die Daten betreffen immer den aktuellen Strom.
  • int read() throws IOException
    Liefert das Zeichen oder –1, wenn das Ende aller Datenströme erreicht ist.
  • int read( byte[] b, int off, int len ) throws IOException
    Liest Zeichen in ein Feld und gibt die Anzahl tatsächlich gelesener Zeichen oder –1 zurück.
  • void close() throws IOException
    Schließt alle Ströme, die vom SequenceInputStream-Objekt eingebunden sind.

Der folgende Programmausschnitt legt mit einem SequenceInputStream den Datenstrom eines ByteArrayInputStream mit einem Datei-Datenstrom zusammen. Es werden anschließend Zeilennummern und Zeileninhalt ausgegeben, wobei sehr schön deutlich wird, dass erst der String und dann die Datei ausgelesen wird. Die Datei muss sich im Pfad befinden, da sie sonst nicht gefunden werden kann.

Listing 14.19 com/tutego/insel/io/stream/SequenceInputStreamDemo.java, main()

String s = "Gezeitenrechnung\nfür\nSchlickrutscher\n"; 
 
InputStream bais = new ByteArrayInputStream( s.getBytes() ); 
InputStream reis = SequenceInputStreamDemo.class.getResourceAsStream( "/lyrics.txt" ); 
 
 
InputStream      sis = new SequenceInputStream( bais, reis ); 
LineNumberReader lnr = new LineNumberReader( new InputStreamReader(sis) ); 
 
for ( String line; (line = lnr.readLine()) != null; ) 
  System.out.printf( "%2d:%s%n", lnr.getLineNumber(), line ); 
}

Zum Ausgeben der Zeilen nutzt das Programm einen LineNumberReader, der neben der Methode readLine() auch die Methode getLineNumber() anbietet. Um für Unicode-Zeichen einen LineNumberReader aber überhaupt verwenden zu können – und keinen veralteten LineNumberInputStream für Bytes –, konvertiert ein InputStreamReader die byte des SequenceInputStream in char für den Reader; es gibt leider keinen SequenceReader.


Galileo Computing - Zum Seitenanfang

14.4.9 Die abstrakte Basisklasse Writer Zur nächsten ÜberschriftZur vorigen Überschrift

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


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

  • protected Writer( Object lock )
    Erzeugt einen Writer-Stream, der sich mit dem übergebenen Synchronisationsobjekt initialisiert. Ist die Referenz null, so gibt es eine NullPointerException.
  • protected Writer()
    Erzeugt einen Writer-Stream, der sich selbst als Synchronisationsobjekt nutzt. Der Konstruktor ist für die Unterklassen interessant, die kein eigenes Lock-Objekt zuordnen wollen.
  • void write( int c ) throws IOException
    Schreibt ein einzelnes Zeichen. Von der 32-Bit-Ganzzahl wird der niedrige Teil (16 Bit des int) geschrieben.
  • void write( char[] cbuf ) throws IOException
    Schreibt ein Feld von Zeichen.
  • abstract void write( char[] cbuf, int off, int len ) throws IOException
    Schreibt len Zeichen des Felds cbuf ab der Position off.
  • void write( String str ) throws IOException
    Schreibt einen String.
  • void write( String str, int off, int len ) throws IOException
    Schreibt len Zeichen der Zeichenkette str ab der Position off.
  • Writer append( char c ) throws IOException
    Hängt ein Zeichen an. Verhält sich wie write(c), nur liefert es, wie die Schnittstelle Append-able verlangt, ein Appendable zurück. Writer ist ein passendes Appendable.
  • Writer append( CharSequence csq ) throws IOException
    Hängt eine Zeichenfolge an. Auch aus der Schnittstelle Appendable.
  • abstract void flush() throws IOException
    Schreibt den internen Puffer. Hängt verschiedene flush()-Aufrufe zu einer Kette zusammen, die sich aus der Abhängigkeit der Objekte ergibt. So werden alle Puffer geschrieben. Aus der Schnittstelle Flushable.
  • 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() löst keine Exception aus. 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()-Methoden 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 Methode 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 Sperrobjekt.


Galileo Computing - Zum Seitenanfang

14.4.10 Die Schnittstelle Appendable Zur nächsten ÜberschriftZur vorigen Überschrift

Alle Writer und auch die Klassen PrintStream, CharBuffer sowie StringBuffer und StringBuilder implementierten die Schnittstelle Appendable, die drei Methoden vorschreibt:


interface java.io.Appendable

  • Appendable append( char c )
    Hängt das Zeichen c an das aktuelle Appendable an und liefert das aktuelle Objekt vom Typ Appendable wieder zurück.
  • Appendable append( CharSequence csq )
    Hängt die Zeichenfolge an dieses Appendable an und liefert es wieder zurück.
  • Appendable append( CharSequence csq, int start, int end )
    Hängt einen Teil der Zeichenfolge an dieses Appendable an und liefert es wieder zurück.

Kovariante Rückgabe in Writer von Appendable

Die Klasse Writer demonstriert gut einen kovarianten Rückgabetyp, also dass der Rückgabetyp einer überschriebenen oder implementierten Methode ebenfalls ein Untertyp sein kann. So verfährt auch Writer, der die Schnittstelle Appendable implementiert. Die Methode append() in Writer besitzt nicht einfach den Rückgabetyp Appendable aus der Schnittstelle Appendable, sondern konkretisiert ihn zu Writer, was ein Appendable ist.

public Writer append( char c ) throws IOException 
{ 
  write( c ); 
  return this; 
}

Galileo Computing - Zum Seitenanfang

14.4.11 Die abstrakte Basisklasse Reader topZur vorigen Überschrift

Die abstrakte Klasse Reader dient zum Lesen von Zeichen aus einem zeichengebenden Eingabestrom. Die einzigen Methoden, die Unterklassen implementieren müssen, sind read(char[], int, int) und close(). Dies entspricht dem Vorgehen bei den Writer-Klassen, die auch nur close() und write(char[], int, int) implementieren müssen. Eine abstrakte flush()-Methode, wie sie Writer besitzt, kann Reader nicht haben. Es bleiben demnach für die Reader-Klasse zwei abstrakte Methoden übrig. Die Unterklassen implementieren jedoch auch andere Methoden aus Geschwindigkeitsgründen neu.


abstract class java.io.Reader 
implements Readable, Closeable

  • protected Reader()
    Erzeugt einen neuen Reader, der sich mit sich selbst synchronisiert.
  • protected Reader( Object lock )
    Erzeugt einen neuen Reader, der mit dem Objekt lock synchronisiert ist.
  • abstract int read( char[] cbuf, int off, int len ) throws IOException
    Liest len Zeichen in den Puffer cbuf ab der Stelle off. Wenn len Zeichen nicht vorhanden sind, wartet der Reader. Gibt die Anzahl gelesener Zeichen zurück oder –1, wenn das Ende des Stroms erreicht wurde.
  • int read( CharBuffer target ) throws IOException
    Liest Zeichen in den CharBuffer. Die Methode schreibt die Schnittstelle Readable vor.
  • int read() throws IOException
    Die parameterlose Methode liest das nächste Zeichen aus dem Eingabestrom. Die Methode wartet, wenn kein Zeichen im Strom bereitliegt. Der Rückgabewert ist ein int im Bereich 0 bis 65.635 (0x0000–0xFFFF). Warum dann der Rückgabewert aber int und nicht char ist, kann leicht damit erklärt werden, dass die Methode den Rückgabewert –1 (0xFFFFFFFF) kodieren muss, falls keine Daten anliegen.
  • int read( char[] cbuf ) throws IOException
    Liest Zeichen aus dem Strom und schreibt sie in ein Feld. Die Methode wartet, bis Eingaben anliegen. Der Rückgabewert ist die Anzahl der gelesenen Zeichen oder –1, wenn das Ende des Datenstroms erreicht wurde.
  • abstract void close() throws IOException
    Schließt den Strom. Folgt anschließend noch ein Aufruf von read(), ready(), mark() oder reset(), lösen diese eine IOException aus. Ein doppelt geschlossener Stream hat keinen weiteren Effekt.

Zu diesen notwendigen Methoden, die bei der Klasse Reader gegeben sind, kommen noch weitere interessante Methode hinzu, die den Status abfragen und Positionen setzen lassen. Die Methode ready() liefert als Rückgabe true, wenn ein read() ohne Blockierung der Eingabe möglich ist. Die Implementierung von Reader gibt immer false zurück. Mit der Methode mark() lässt sich eine bestimmte Position innerhalb des Eingabestroms markieren. Die Methode sichert dabei die Position. Mit beliebigen reset()-Aufrufen lässt sich diese konkrete Stelle zu einem späteren Zeitpunkt wieder anspringen. mark() besitzt einen Ganzzahl-Parameter, der angibt, wie viele Zeichen gelesen werden dürfen, bevor die Markierung nicht mehr gültig ist. Die Zahl ist wichtig, da sie die interne Größe des Puffers bezeichnet, der für den Strom angelegt werden muss. Nicht jeder Datenstrom unterstützt dieses Hin- und Herspringen. Die Klasse StringReader unterstützt etwa die Markierung einer Position, die Klasse FileReader dagegen nicht. Daher sollte vorher mit markSupported() überprüft werden, ob das Markieren auch unterstützt wird. Wenn der Datenstrom es nicht unterstützt und wir diese Warnung ignorieren, werden wir eine IOException bekommen. Denn Reader implementiert mark() und reset() ganz einfach und muss von uns im Bedarfsfall überschrieben werden.

public void mark(int readAheadLimit) throws IOException { 
  throw new IOException("mark() not supported"); 
} 
public void reset() throws IOException { 
  throw new IOException("reset() not supported"); 
}

Daher gibt markSupported() auch in der Reader-Klasse false zurück.

  • long skip( long n ) throws IOException Überspringt n Zeichen. Blockt, bis Zeichen vorhanden sind. Gibt die Anzahl der wirklich übersprungenen Zeichen zurück.
  • public boolean ready() throws IOException
    true
    , wenn aus dem Stream direkt gelesen werden kann. Das heißt allerdings nicht, dass false immer Blocken bedeutet.
  • boolean markSupported()
    Der Stream unterstützt die mark()-Operation.
  • void mark( int readAheadLimit ) throws IOException
    Markiert eine Position im Stream. Der Parameter bestimmt, nach wie vielen Zeichen die Markierung ungültig wird, mit anderen Worten: Er gibt die Puffergröße an.
  • void reset() throws IOException
    Falls eine Markierung existiert, setzt der Stream an der Markierung an. Wurde die Position vorher nicht gesetzt, dann wird eine IOException mit dem String »Stream not marked« ausgelöst.

Reader implementiert die schon bekannte Schnittstelle Closeable mit der Methode close(). Und so, wie ein Writer die Schnittstelle Appendable implementiert, so implementiert ein Reader die Schnittstelle Readable und damit die Operation int read(CharBuffer target) throws IOException.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.






<< zurück
  Zum Katalog
Zum Katalog: Java ist auch eine Insel





Java ist auch eine Insel
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Tipp
Zum Katalog: Coding for Fun





 Coding for Fun


 Buchempfehlungen
Zum Katalog: Objektorientierte Programmierung





 Objektorientierte
 Programmierung


Zum Katalog: Einstieg in Eclipse 3.4






 Einstieg in
 Eclipse 3.4


Zum Katalog: Java 6 lernen mit Eclipse






 Java 6 lernen
 mit Eclipse


Zum Katalog: NetBeans Platform 6






 NetBeans
 Platform 6


Zum Katalog: Java und XML






 Java und XML


Zum Katalog: Visual C# 2008






 Visual C# 2008


Zum Katalog: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


Zum Katalog: C++ von A bis Z






 C++ von A bis Z


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Galileo Press 2009
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, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de