Streams
Klassenhierarchie: Input Streams
Man erkennt an der Klassenhierarchie gut den Aufbau. Ausgehend von der Basisklasse InputStream
, die alle grundlegenden Operationen auf den lesenden Streams festlegt, gibt es für die unterschiedlichen Datenquellen spezielle Subklassen. Diese sind:
FileInputStream
– DateienZipInputStream
– Zip-DateienSocketInputStream
– Netzwerk-SocketsObjectInputStream
– SerialisierungPipedInputStream
– Verbindung von ThreadsByteArrayInputStream
– Lesen aus einem Byte-ArrayFilterInputStream
– Basisklasse für FilterBufferedInputStream
– PufferungDataInputStream
– Lesen von Daten
Die Methoden von InputStream
abstract int read()
– liest ein einzelnes Byte vom Streamint read(byte[] b)
– liest vom Stream in das Byte-Arrayint read(byte[] b, int off, int len)
– liest maximallen
Bytes in das Byte-Array ab positionoff
long skip(long n)
– überspringt n Bytesint available()
– Anzahl der Bytes die voraussichtlich gelesen werden können ohne das die Leseoperation blockiertvoid close()
– schließt den Stream
void mark(int readlimit)
– markiert die aktuelle Position im Stream und vergisst sie erst nach readlimit Bytes die daraufhin gelesen wurdenvoid reset()
– springt zu der mitmark()
markierten Stelle im Stream zurückboolean markSupported()
– zeigt an, ob der Stream überhaupt das setzen einer Marke mitmark()
unterstützt
Bezüglich der genauen Arbeitsweise der Methoden und ihrer Dokumentation sei auf die JavaDoc der Klassen verwiesen.
Bemerkenswert ist aber die Tatsache, dass die Methode read()
ein int
als Rückgabetyp hat und kein byte
, obwohl ein InputStream
die Daten byteweise anliefert. Der größere Datentyp ist nötig, da durch eine -1
signalisiert wird, dass der Stream zu Ende ist. Da aber -1
ein gültiger Byte-Wert ist, hat man hier den größeren Datentyp nehmen müssen. Aus diesem Grund ist es falsch den Rückgabewert von read()
vor dem Vergleich mit -1
zu casten. Erst muss mit -1
(als int
) verglichen werden, dann gecastet, sonst bricht der Lesevorgang möglicherweise zu früh ab, nämlich wenn in den Daten zufällig ein 0xFF
vorkommt, das vorzeichenbehaftet einer -1
entspricht.
Bytes von einem Stream lesen
InputStream fis = new FileInputStream("/tmp/test.txt");
int daten;
while ((daten = fis.read()) != -1) {
byte b = (byte) daten;
// jetzt kann man etwas sinnvolles mit den Bytes machen, die aus
// der Datei gelesen wurden
}
fis.close();
In diesem Beispiel wird die -1 richtig behandelt. Erst nach dem Vergleich erfolgt der cast.
InputStream fis = new FileInputStream("/tmp/test.txt");
byte b;
while ((b = (byte) fis.read()) != -1) { // FEHLER!!
// jetzt kann man etwas sinnvolles mit den Bytes machen, die aus
// der Datei gelesen wurden
}
fis.close();
In diesem Beispiel wird die -1
falsch behandelt. Es wird erst gecastet und dann verglichen. Der Lesevorgang wird potenziell zu früh beendet, weil -1
ein möglicher Byte-Wert ist und so der Lesevorgang abbricht, wenn dieser Wert in der Quelle auftritt.
Blöcke von einem Stream lesen
InputStream fis = new FileInputStream("/tmp/test.txt");
byte[] daten = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(daten)) > -1) {
// jetzt kann man etwas sinnvolles mit den Bytes machen, die aus
// der Datei gelesen wurden
}
fis.close();
Das Lesen einzelner Bytes ist ineffizient, daher bietet InputStream
eine Methode an, mit der man die Bytes blockweise in ein Byte-Array lesen kann. Hier signalisiert der Rückgabewert der Methode, wie viele Bytes gelesen wurden. -1
zeigt das Ende des Streams an. 0
ist ein korrekter Rückgabewert, da es beim Lesen vom Netzwerk vorkommen kann, dass noch keine neuen Daten eingetroffen sind, die Verbindung aber noch steht. Hier muss man einfach erneut read
aufrufen.
Klassenhierarchie: Output Streams
Auch bei den OutputStreams gibt es eine Basisklasse, die das Verhalten festlegt und eine Reihe von konkreten Klassen für die verschiedenen Senken. Diese sind:
FileOutputStream
– DateienSocketOutputStream
– Netzwerk-SocketsObjectOutputStream
– SerialisierungPipedOutputStream
– Verbindung von ThreadsFilterOutputStream
– Basisklasse für FilterByteArrayOutputStream
– Schreiben in ein Byte-ArrayBufferedOutputStream
– PufferungDataOutputStream
– Schreiben von Daten
Die Methoden von OutputStream
abstract void write(int b)
– schreibt ein Bytevoid write(byte[] b)
– schreibt das gesamte Byte-Arrayvoid write(byte[] b, int off, int len)
– schreibtlen
Bytes aus dem Byte-Array beginnend beioff
void flush()
– leert interne Puffer und bringt die Daten auf die Plattevoid close()
– schließt den Stream (impliziert einflush()
)
Auch hier sei auf die JavaDoc der Klassen verwiesen. Die Tatsache, dass die write
-Methode einen int
als Parameter nimmt, hat keine besondere Bedeutung, sondern dient nur dazu, dass sie symmetrisch zur read-Methode ist. Hierdurch kann man den Wert, der von read
gelesen wurde direkt write
übergeben ohne ihn konvertieren zu müssen.
Bytes auf einen Stream schreiben
OutputStream fos = new FileOutputStream("/tmp/myfile");
fos.write(0xca);
fos.write(0xfe);
fos.write(0xba);
fos.write(0xbe);
fos.close();

Blöcke auf einen Stream schreiben
OutputStream fos = new FileOutputStream("/tmp/myfile");
byte[] daten = { (byte) 0xca, (byte) 0xfe,
(byte) 0xba, (byte) 0xbe };
fos.write(daten);
fos.write(daten, 0, 2);
fos.write(daten, 0, 2);
fos.write(daten, 2, 2);
fos.write(daten, 2, 2);
fos.close();

FileInputStream
FileInputStream
public FileInputStream(String name)
throws FileNotFoundException
public FileInputStream(File file)
throws FileNotFoundException
FileInputStream
erbt alle Methoden von InputStream
und fügt nur zwei Konstruktoren hinzu, um eine Datei zum Öffnen auswählen zu können. Man kann die Datei entweder über den Dateinamen (als String
) oder ein spezielles File
-Objekt öffnen (das noch später erläutert wird).
FileOutputStream
FileOutputStream
public FileOutputStream(String name)
throws FileNotFoundException
public FileOutputStream(String name, boolean append)
throws FileNotFoundException
public FileOutputStream(File file)
throws FileNotFoundException
public FileOutputStream(File file, boolean append)
throws FileNotFoundException
FileOutputStream
verhält sich analog zum FileInputStream
nur dass hier über einen weiteren Konstruktorparameter append
angegeben werden kann, ob die Datei überschrieben (false
) werden soll oder die geschriebenen Daten hinten an die Datei angehängt werden sollen (true
).
Beispiel: Anhängen an Stream
byte[] daten = { (byte) 0xca, (byte) 0xfe,
(byte) 0xba, (byte) 0xbe };
OutputStream fos = new FileOutputStream("/tmp/myfile");
fos.write(daten);
fos.close();
OutputStream fos2 = new FileOutputStream("/tmp/myfile", true);
fos2.write(daten);
fos2.close();

Beispiel: Einfaches Kopierprogramm
// Quell und Zieldatei
String quelle = "/tmp/quelle.txt";
String ziel = "/tmp/ziel.txt";
// Streams für Quell und Zieldatei
InputStream in = new FileInputStream(quelle);
OutputStream out = new FileOutputStream(ziel);
byte[] buffer = new byte[1024]; // Puffer für Dateiinhalt
int gelesen; // Anzahl der gelesenen Bytes
// Daten aus Quell- in Zieldatei kopieren
while ((gelesen = in.read(buffer)) > -1) {
out.write(buffer, 0, gelesen);
}
// Streams schließen
in.close();
out.close();
Beispiel: Naive Prüfsummenbestimmung
InputStream in = new FileInputStream("/tmp/quelle.txt");
ByteArrayOutputStream out = new ByteArrayOutputStream();
int daten;
while ((daten = in.read()) > -1) {
out.write(daten);
}
in.close();
out.close();
byte[] bytes = out.toByteArray();
long sum = 0;
for (int i = 0; i < bytes.length; i++) {
sum += bytes[i];
}
System.out.println(sum);
Filter Streams und Decorator Pattern
- Zusätzlich zu den Streams, die direkt mit einer Quelle oder Senke (Datei, Socket etc.) verbunden sind, gibt es die Filter-Streams
- Filter-Streams können hinter einen anderen Stream geschaltet werden (Decorator Pattern) und können die Daten entsprechend verändern (daher Filter-Streams)
Wenn man mehrere Streams miteinander verkettet, fließen die Daten von der Quelle (oder zur Senke) nacheinander durch die Streams. Diese können die Daten dann entsprechend verändern oder umwandeln.
Beispiel: Buffering
String quelle = "/tmp/quelle.txt";
String ziel = "/tmp/ziel.txt";
InputStream in = new BufferedInputStream(
new FileInputStream(quelle));
OutputStream out = new BufferedOutputStream(
new FileOutputStream(ziel));
byte[] buffer = new byte[1024];
int gelesen;
while ((gelesen = in.read(buffer)) > -1) {
out.write(buffer, 0, gelesen);
}
in.close();
out.close();
Ein häufig eingesetzter Filter-Stream ist der BufferedInputStream bzw. der BufferedOutputStream. Diese puffern (buffer) die Daten und erlauben so eine schnellere Verarbeitung, weil nicht für jede Operation bis auf die Quelle/Senke durchgegriffen werden muss.
DataOutputStream / DataInputStream
- DataOutputStream schreibt primitive Datentypen und String in einen anderen Stream und erlaubt so den plattformunabhängigen Datenaustausch
- DateInputStream liest die von
DataOutputStream
geschriebenen Daten wieder ein
Man kann zwar mit den normalen Streams problemlos byte-weise beliebige Daten schreiben und lesen, aber die Umwandlung der längeren Datentypen (short
, char
, int
, long
, float
, double
) obliegt dann den Programmierer:innen. Dies ist insofern problematisch, als z. B. die Reihenfolge der Bytes eines Long nicht plattformübergreifend festgelegt ist – Stichwort little und big endian.
Die Mühe der Umwandlung kann man sich sparen, wenn man DataOutputStream
und DataInputStream
verwendet. Hier sind für alle primitiven Datentypen und Strings entsprechende Methoden vorgesehen, um sie in einem einheitlichen Format zu lesen und zu schreiben.
Methoden von DataOutputStream
void writeBoolean(boolean v) throws IOException
void writeByte(int v) throws IOException
void writeShort(int v) throws IOException
void writeChar(int v) throws IOException
void writeInt(int v) throws IOException
void writeLong(long v) throws IOException
void writeFloat(float v) throws IOException
void writeDouble(double v) throws IOException
void writeBytes(String s) throws IOException
void writeChars(String s) throws IOException
void writeUTF(String s) throws IOException
Beispiel: DataOutputStream
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("/tmp/daten")));
out.writeUTF("** Datendatei **");
out.writeUTF("Datum");
out.writeLong(new Date().getTime());
out.writeUTF("PI");
out.writeDouble(Math.PI);
out.close();

Methoden von DataInputStream
boolean readBoolean() throws IOException
byte readByte() throws IOException
int readUnsignedByte() throws IOException
short readShort() throws IOException
int readUnsignedShort() throws IOException
char readChar() throws IOException
int readInt() throws IOException
long readLong() throws IOException
float readFloat() throws IOException
double readDouble() throws IOException
String readUTF() throws IOException
Beispiel: DataInputStream
DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("/tmp/daten")));
String header = dis.readUTF();
String datumTag = dis.readUTF();
Date datum = new Date(dis.readLong());
String PITag = dis.readUTF();
double pi = dis.readDouble();
dis.close();
System.out.println(header);
System.out.println(datumTag + " " + datum);
System.out.println(PITag + " " + pi);
** Datendatei **
Datum Sun Sep 12 22:49:27 CEST 2010
PI 3.141592653589793