1. Datenströme
Selten existiert ein Programm als eigenständige Insel. Programme interagieren normalerweise irgendwie mit der „Außenwelt“. Dies kann durch das Lesen von Daten von der Tastatur, das Senden von Nachrichten, das Herunterladen von Seiten aus dem Internet oder umgekehrt durch das Hochladen von Dateien auf einen Remote-Server geschehen.
Wir können alle diese Verhaltensweisen mit einem Wort zusammenfassen: Datenaustausch zwischen dem Programm und der Außenwelt. Warten Sie, das ist nicht nur ein Wort.
Natürlich kann der Datenaustausch selbst in zwei Teile unterteilt werden: Daten empfangen und Daten senden. Sie lesen beispielsweise Daten von der Tastatur mithilfe eines Scanner
Objekts aus – dieses empfängt Daten. Und Sie zeigen Daten mit einem Befehl auf dem Bildschirm an System.out.println()
– das ist das Senden von Daten.
In der Programmierung wird der Begriff „Stream“ zur Beschreibung des Datenaustauschs verwendet. Woher kommt dieser Begriff?
Im wirklichen Leben kann es einen Wasserstrahl oder einen Bewusstseinsstrom geben. Beim Programmieren gibt es Datenströme .
Streams sind ein vielseitiges Werkzeug. Sie ermöglichen dem Programm, Daten von überall zu empfangen (Eingabeströme) und Daten überall zu senden (Ausgabeströme). Es gibt also zwei Arten:
- Ein Eingabestream dient zum Empfangen von Daten
- Ein Ausgabestream dient zum Senden von Daten
Um Streams „greifbar“ zu machen, haben die Entwickler von Java zwei Klassen geschrieben: InputStream
und OutputStream
.
Die InputStream
Klasse verfügt über eine read()
Methode, mit der Sie Daten daraus lesen können. Und die OutputStream
Klasse verfügt über eine write()
Methode, mit der Sie Daten in sie schreiben können. Sie haben auch andere Methoden, aber dazu später mehr.
Byteströme
Über welche Art von Daten sprechen wir? Welches Format braucht es? Mit anderen Worten: Welche Datentypen unterstützen diese Klassen?
Da es sich hierbei um generische Klassen handelt, unterstützen sie den gebräuchlichsten Datentyp – den byte
. Ein OutputStream
kann Bytes (und Byte-Arrays) schreiben und ein InputStream
Objekt kann Bytes (oder Byte-Arrays) lesen. Das ist alles – sie unterstützen keine anderen Datentypen.
Daher werden diese Streams auch Bytestreams genannt .
Ein Merkmal von Streams ist, dass ihre Daten nur sequentiell gelesen (oder geschrieben) werden können. Sie können keine Daten aus der Mitte eines Streams lesen, ohne alle Daten davor zu lesen.
So funktioniert das Lesen von Daten über die Tastatur in der Scanner
Klasse: Sie lesen Daten nacheinander Zeile für Zeile von der Tastatur. Wir lesen eine Zeile, dann die nächste Zeile, dann die nächste Zeile und so weiter. Passenderweise heißt die Methode zum Lesen von Zeilen nextLine()
.
Das Schreiben von Daten OutputStream
erfolgt ebenfalls sequentiell. Ein gutes Beispiel hierfür ist die Konsolenausgabe. Sie geben eine Zeile aus, gefolgt von einer weiteren und einer weiteren. Dies ist eine sequentielle Ausgabe. Sie können nicht die erste Zeile, dann die zehnte und dann die zweite ausgeben. Alle Daten werden nur sequentiell in einen Ausgabestream geschrieben.
Charakterströme
Sie haben kürzlich erfahren, dass Zeichenfolgen der zweitbeliebteste Datentyp sind, und das ist auch der Fall. Viele Informationen werden in Form von Zeichen und ganzen Zeichenfolgen weitergegeben. Ein Computer ist hervorragend darin, alles als Bytes zu senden und zu empfangen, aber Menschen sind nicht so perfekt.
Um dieser Tatsache Rechnung zu tragen, haben Java-Programmierer zwei weitere Klassen geschrieben: Reader
und Writer
. Die Reader
Klasse ist analog zur InputStream
Klasse, aber ihre read()
Methode liest keine Bytes, sondern Zeichen ( char
). Die Writer
Klasse entspricht der OutputStream
Klasse. Und genau wie die Reader
Klasse funktioniert sie mit Zeichen ( char
), nicht mit Bytes.
Wenn wir diese vier Klassen vergleichen, erhalten wir folgendes Bild:
Bytes (Byte) | Zeichen (char) | |
---|---|---|
Daten lesen |
|
|
Daten schreiben |
|
|
Praktische Anwendung
Die Klassen InputStream
, und selbst werden von niemandem direkt verwendet, da sie keinen konkreten Objekten zugeordnet sind, aus denen Daten gelesen (oder in die Daten geschrieben werden können) werden OutputStream
. Aber diese vier Klassen haben viele Nachkommenklassen, die viel können.Reader
Writer
2. InputStream
Klasse
Die InputStream
Klasse ist interessant, weil sie die übergeordnete Klasse für Hunderte von Nachkommenklassen ist. Es verfügt über keine eigenen Daten, aber über Methoden, die alle von ihm abgeleiteten Klassen erben.
Im Allgemeinen ist es selten, dass Stream-Objekte Daten intern speichern. Ein Stream ist ein Werkzeug zum Lesen/Schreiben von Daten, aber nicht zum Speichern. Allerdings gibt es Ausnahmen.
Methoden der InputStream
Klasse und aller ihrer Nachkommenklassen:
Methoden | Beschreibung |
---|---|
|
Liest ein Byte aus dem Stream |
|
Liest ein Array von Bytes aus dem Stream |
|
Liest alle Bytes aus dem Stream |
|
Überspringt n Bytes im Stream (liest und verwirft sie) |
|
Überprüft, wie viele Bytes noch im Stream vorhanden sind |
|
Schließt den Stream |
Lassen Sie uns diese Methoden kurz durchgehen:
read()
Methode
Die read()
Methode liest ein Byte aus dem Stream und gibt es zurück. int
Der Rückgabetyp könnte Sie verwirren . Dieser Typ wurde ausgewählt, da int
es sich um den Standard-Integer-Typ handelt. Die ersten drei Bytes des int
werden Null sein.
read(byte[] buffer)
Methode
Dies ist die zweite Variante der read()
Methode. Damit können Sie ein Byte-Array InputStream
auf einmal aus einem Array lesen. Das Array, das die Bytes speichert, muss als Argument übergeben werden. Die Methode gibt eine Zahl zurück – die Anzahl der tatsächlich gelesenen Bytes.
Nehmen wir an, Sie haben einen 10-Kilobyte-Puffer und lesen mithilfe der FileInputStream
Klasse Daten aus einer Datei. Wenn die Datei nur 2 Kilobyte enthält, werden alle Daten in das Pufferarray geladen und die Methode gibt die Zahl 2048 (2 Kilobyte) zurück.
readAllBytes()
Methode
Eine sehr gute Methode. Es liest einfach alle Daten aus dem, InputStream
bis sie aufgebraucht sind, und gibt sie als Einzelbyte-Array zurück. Dies ist sehr praktisch zum Lesen kleiner Dateien. Große Dateien passen möglicherweise physisch nicht in den Speicher und die Methode löst eine Ausnahme aus.
skip(long n)
Methode
Mit dieser Methode können Sie die ersten n Bytes des InputStream
Objekts überspringen. Da die Daten streng sequentiell gelesen werden, liest diese Methode einfach die ersten n Bytes aus dem Stream und verwirft sie.
Gibt die Anzahl der tatsächlich übersprungenen Bytes zurück (falls der Stream endete, bevor n
Bytes übersprungen wurden).
int available()
Methode
Die Methode gibt die Anzahl der Bytes zurück, die noch im Stream verbleiben
void close()
Methode
Die close()
Methode schließt den Datenstrom und gibt die damit verbundenen externen Ressourcen frei. Sobald ein Stream geschlossen ist, können keine Daten mehr daraus gelesen werden.
Schreiben wir ein Beispielprogramm, das eine sehr große Datei kopiert. Wir können die Methode nicht verwenden readAllBytes()
, um die gesamte Datei in den Speicher einzulesen. Beispiel:
Code | Notiz |
---|---|
|
InputStream zum Lesen aus der Datei OutputStream zum Schreiben in die Datei Puffer, in den wir die Daten einlesen werden Solange Daten im Stream vorhanden sind Daten in den Puffer lesen Die Daten aus dem Puffer in den zweiten Stream schreiben |
In diesem Beispiel haben wir zwei Klassen verwendet: FileInputStream
ist ein Nachkomme von InputStream
zum Lesen von Daten aus einer Datei und FileOutputStream
ein Nachkomme von OutputStream
zum Schreiben von Daten in eine Datei. Über die zweite Klasse sprechen wir etwas später.
Ein weiterer interessanter Punkt ist hier die real
Variable. Wenn der letzte Datenblock aus einer Datei gelesen wird, kann er leicht weniger als 64 KB Daten enthalten. Dementsprechend müssen wir nicht den gesamten Puffer ausgeben, sondern nur einen Teil davon – die ersten real
Bytes. Genau das passiert in der write()
Methode.
3. Reader
Klasse
Die Reader
Klasse ist ein vollständiges Analogon der InputStream
Klasse. Der einzige Unterschied besteht darin, dass es mit Zeichen ( char
) und nicht mit Bytes funktioniert. Genau wie die InputStream
Klasse Reader
wird die Klasse nirgendwo allein verwendet: Sie ist die übergeordnete Klasse für Hunderte von Nachkommenklassen und definiert für alle gemeinsame Methoden.
Methoden der Reader
Klasse (und aller ihrer Nachkommenklassen):
Methoden | Beschreibung |
---|---|
|
Liest einen char aus dem Stream |
|
Liest ein char Array aus dem Stream |
|
Sprünge n chars im Stream (liest und verwirft sie) |
|
Überprüft, ob noch etwas im Stream vorhanden ist |
|
Schließt den Stream |
Die Methoden sind denen der InputStream
Klasse sehr ähnlich, es gibt jedoch leichte Unterschiede.
int read()
Methode
Diese Methode liest einen char
aus dem Stream und gibt ihn zurück. Der char
Typ wird zu einem erweitert int
, aber die ersten beiden Bytes des Ergebnisses sind immer Null.
int read(char[] buffer)
Methode
Dies ist die zweite Variante der read()
Methode. Damit können Sie ein char-Array Reader
auf einmal aus einem Array lesen. Das Array, in dem die Zeichen gespeichert werden, muss als Argument übergeben werden. Die Methode gibt eine Zahl zurück – die Anzahl der tatsächlich gelesenen Zeichen.
skip(long n)
Methode
Mit dieser Methode können Sie die ersten n Zeichen des Reader
Objekts überspringen. Es funktioniert genauso wie die analoge Methode der InputStream
Klasse. Gibt die Anzahl der tatsächlich übersprungenen Zeichen zurück.
boolean ready()
Methode
Gibt zurück true
, wenn der Stream ungelesene Bytes enthält.
void close()
Methode
Die close()
Methode schließt den Datenstrom und gibt die damit verbundenen externen Ressourcen frei. Sobald ein Stream geschlossen ist, können keine Daten mehr daraus gelesen werden.
Zum Vergleich schreiben wir ein Programm, das eine Textdatei kopiert:
Code | Notiz |
---|---|
|
Reader zum Lesen aus einer Datei Writer zum Schreiben in eine Datei Puffer, in den wir die Daten einlesen werden Solange Daten im Stream vorhanden sind Daten in einen Puffer lesen Die Daten aus dem Puffer in den zweiten Stream schreiben |
GO TO FULL VERSION