09.03.02 java.io.InputStream
Der java.io.InputStream ist die Basisklasse aller lesenden Datenströme. Dazu gehört z. B. das Lesen von binären Daten von der Festplatte (bspw. Bilder und Dateien), aus dem Netzwerk (Datenübertragung zwischen zwei Computern), oder das Auslesen einer Eingabe des Benutzers in der Konsole.
Wie Sie der API-Dokumentation (Link|Artikel) entnehmen können, handelt es sich hierbei um eine abstrakte Klasse. Sie kann also nicht sofort verwendet und somit auch nicht über das Schlüsselwort new instanziiert werden. Stattdessen wird eine Kindklasse benötigt, die von java.io.InputStream erbt und die fehlenden Methoden implementiert. Ebenfalls wird aus der Dokumentation erkenntlich, dass die einzige abstrakte Methode read() lautet. Sehen Sie sich aber zuerst einmal alle Methoden dieser abstrakten Klasse an:
- available() – Gibt einen Schätzung zurück, wie viele Bytes von diesem Stream noch gelesen werden können, bevor der Stream „blockiert“ wird. Beim Lesen wartet ein Stream gewöhnlich solange, bis er von seiner Gegenstelle (der Quelle, aus die er liest) das nächste Byte zugeschickt bekommt. Bis das nächste Byte noch nicht angekommen ist, wird der Stream (und somit auch der aktuelle Thread) blockiert.
- close() – Schließt den
InputStreamund gibt alle benötigten Systemressourcen (z. B. die Sperrung einer Datei, wenn diese von einem Stream gelesen wird) wieder frei. Es ist in den meisten Fällen sehr wichtig, dass ein Stream nach seiner Verwendung wieder ordnungsgemäß geschlossen wird. - mark(int readlimit) – Markiert die aktuelle Position.
readlimitgibt an, wie viele Bytes gelesen werden können, bevor die Markierung ungültig ist. Siehe hierzu auchreset(). - markSupported() – Gibt einen boolschen Wert zurück, ob die Methoden
markund somit auchresetvon diesem Stream unterstützt werden. - read() – Liest das nächste Byte aus der Datenquelle und muss als einzige Methode zwingend beim Programmieren eines eigenen
InputStreamsüberschrieben werden. Gibt -1 zurück, falls das Ende des Streams erreicht wurde. - read(byte[] b) – Liest die nächsten n Bytes (Länge des Byte-Arrays) und speichert diese im übergebenen Byte-Array. Sind keine weiteren Daten mehr in der Datenquelle vorhanden, liefert auch diese Methode -1 zurück.
- read(byte[] b, int off, int len) – Ähnliche Funktionalität wie
read(byte[] b), nur dass noch ein Offset (ab welcher Stelle wird in das Byte-Array geschrieben?) und eine Länge (wie viele Bytes werden in das Array geschrieben?) mit übergeben werden können. - reset() – Setzt den
InputStreaman die Stelle zurück, die zuletzt mitmark(int readlimit)markiert wurde. - skip(long n) – Überspringt die nächsten
nBytes. Der Rückgabewert entspricht den tatsächlich übersprungenen Bytes.
Dass die read()-Methode überschrieben werden muss, ist nun klar. Immerhin liest sie das nächste Byte aus einer beliebigen Quelle (z. B. einer Netzwerkverbindung, Konsole oder Datei), die durch die jeweilige Implementierung von InputStream festgelegt wird. Aber warum müssen die anderen Methoden nicht zwingend implementiert werden? Sehen wir uns hierzu den Quellcode der abstrakten Klasse java.io.InputStream an (zur Übersichtlichkeit wurden die Kommentare entfernt):
package java.io;
public abstract class InputStream implements Closeable {
private static final int SKIP_BUFFER_SIZE = 2048;
private static byte[] skipBuffer;
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
oreturn read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
oif (b == null) {
o throw new NullPointerException();
o} else if (off < 0 || len < 0 || len > b.length - off) {
o throw new IndexOutOfBoundsException();
o} else if (len == 0) {
o return 0;
o}
oint c = read();
oif (c == -1) {
o return -1;
o}
ob[off] = (byte)c;
oint i = 1;
otry {
o for (; i < len ; i++) {
oc = read();
oif (c == -1) {
o break;
o}
ob[off + i] = (byte)c;
o }
o} catch (IOException ee) {
o}
oreturn i;
}
public long skip(long n) throws IOException {
olong remaining = n;
oint nr;
oif (skipBuffer == null)
o skipBuffer = new byte[SKIP_BUFFER_SIZE];
obyte[] localSkipBuffer = skipBuffer;
o
oif (n <= 0) {
o return 0;
o}
owhile (remaining > 0) {
o nr = read(localSkipBuffer, 0,
o (int) Math.min(SKIP_BUFFER_SIZE, remaining));
o if (nr < 0) {
obreak;
o }
o remaining -= nr;
o}
o
oreturn n - remaining;
}
public int available() throws IOException {
oreturn 0;
}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {
othrow new IOException("mark/reset not supported");
}
public boolean markSupported() {
oreturn false;
}
}
Wie Sie sehen, verwenden die beiden anderen lesenden Methoden und skip(long n) die abstrakte read-Methode, um ihre Funktionalitäten zu erfüllen. Die Methoden mark(int readlimit) und reset() sind hingegen erst einmal ausgeschaltet und close() geht davon aus, dass es keine Ressourcen gibt, die freigegeben werden müssen. Natürlich steht es Ihnen frei, beliebig viele Methoden dieser Klasse bei einer Implementierung zu überschreiben. Immer je nach Anforderung.
Zum Abschluss dieses Kapitels programmieren wir einen simplen StringInputStream, der als Datenquelle einen einfachen String besitzt:
package de.jbb;
import java.io.InputStream;
public class StringInputStream extends InputStream {
// Speichert die aktuelle Position in unserem Stream
private int position;
// Die Quelle, aus der gelesen werden soll
private String source;
public StringInputStream(String source) {
this.source = source;
}
@Override
public int read() {
// Überprüfung, ob noch Daten in der Quelle vorhanden sind
if (this.source.length() > this.position) {
// Char (wird automatisch in ein Byte umgewandelt)
// an der aktuellen Position zurückgeben und
// gleichzeitig die nächste Position im Stream setzen
return this.source.charAt(this.position++);
}
// Keine Daten mehr vorhanden, -1 zurückgeben.
return -1;
}
}
Zum Testen können Sie bspw. diesen Code verwenden:
StringInputStream sis = new StringInputStream("Ich bin ein Datenquelle für einen InputStream");
int curByte = 0;
while ((curByte = sis.read()) != -1) {
System.out.print((char)curByte);
}
Nützlich wird unser StringInputStream immer dann, wenn eine externe Methode Daten in Form eines InputStreams erwartet, die Daten aber bereits intern als String vorliegen.