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 InputStream und 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. readlimit gibt an, wie viele Bytes gelesen werden können, bevor die Markierung ungültig ist. Siehe hierzu auch reset().
  • markSupported() – Gibt einen boolschen Wert zurück, ob die Methoden mark und somit auch reset von 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 InputStream an die Stelle zurück, die zuletzt mit mark(int readlimit) markiert wurde.
  • skip(long n) – Überspringt die nächsten n Bytes. 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 {
	return read(b, 0, b.length);
  }

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

	int c = read();
	if (c == -1) {
	  return -1;
	}
	b[off] = (byte)c;

	int i = 1;
	try {
	  for (; i < len ; i++) {
		c = read();
		if (c == -1) {
		  break;
		}
		b[off + i] = (byte)c;
	  }
	} catch (IOException ee) {
	}
	return i;
  }

  public long skip(long n) throws IOException {

	long remaining = n;
	int nr;
	if (skipBuffer == null)
	  skipBuffer = new byte[SKIP_BUFFER_SIZE];

	byte[] localSkipBuffer = skipBuffer;
		
	if (n <= 0) {
	  return 0;
	}

	while (remaining > 0) {
	  nr = read(localSkipBuffer, 0,
		    (int) Math.min(SKIP_BUFFER_SIZE, remaining));
	  if (nr < 0) {
		break;
	  }
	  remaining -= nr;
	}
	
	return n - remaining;
  }

  public int available() throws IOException {
	return 0;
  }

  public void close() throws IOException {}

  public synchronized void mark(int readlimit) {}

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

  public boolean markSupported() {
	return 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.

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.