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 ScannerObjekts 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: InputStreamund OutputStream.

Die InputStreamKlasse verfügt über eine read()Methode, mit der Sie Daten daraus lesen können. Und die OutputStreamKlasse 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 OutputStreamkann Bytes (und Byte-Arrays) schreiben und ein InputStreamObjekt 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 ScannerKlasse: 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 OutputStreamerfolgt 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: Readerund Writer. Die ReaderKlasse ist analog zur InputStreamKlasse, aber ihre read()Methode liest keine Bytes, sondern Zeichen ( char). Die WriterKlasse entspricht der OutputStreamKlasse. Und genau wie die ReaderKlasse funktioniert sie mit Zeichen ( char), nicht mit Bytes.

Wenn wir diese vier Klassen vergleichen, erhalten wir folgendes Bild:

Bytes (Byte) Zeichen (char)
Daten lesen
InputStream
Reader
Daten schreiben
OutputStream
Writer

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.ReaderWriter


2. InputStreamKlasse

Die InputStreamKlasse 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 InputStreamKlasse und aller ihrer Nachkommenklassen:

Methoden Beschreibung
int read()
Liest ein Byte aus dem Stream
int read(byte[] buffer)
Liest ein Array von Bytes aus dem Stream
byte[] readAllBytes()
Liest alle Bytes aus dem Stream
long skip(long n)
Überspringt nBytes im Stream (liest und verwirft sie)
int available()
Überprüft, wie viele Bytes noch im Stream vorhanden sind
void close()
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. intDer Rückgabetyp könnte Sie verwirren . Dieser Typ wurde ausgewählt, da intes sich um den Standard-Integer-Typ handelt. Die ersten drei Bytes des intwerden Null sein.

read(byte[] buffer)Methode

Dies ist die zweite Variante der read()Methode. Damit können Sie ein Byte-Array InputStreamauf 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 FileInputStreamKlasse 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, InputStreambis 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 InputStreamObjekts ü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 nBytes ü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
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);
FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = new byte[65536]; // 64Kb
   while (input.available() > 0)
   {
      int real = input.read(buffer);
      output.write(buffer, 0, real);
   }
}



InputStreamzum Lesen aus der Datei
OutputStreamzum 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: FileInputStreamist ein Nachkomme von InputStreamzum Lesen von Daten aus einer Datei und FileOutputStreamein Nachkomme von OutputStreamzum Schreiben von Daten in eine Datei. Über die zweite Klasse sprechen wir etwas später.

Ein weiterer interessanter Punkt ist hier die realVariable. 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 realBytes. Genau das passiert in der write()Methode.



3. ReaderKlasse

Die ReaderKlasse ist ein vollständiges Analogon der InputStreamKlasse. Der einzige Unterschied besteht darin, dass es mit Zeichen ( char) und nicht mit Bytes funktioniert. Genau wie die InputStreamKlasse Readerwird die Klasse nirgendwo allein verwendet: Sie ist die übergeordnete Klasse für Hunderte von Nachkommenklassen und definiert für alle gemeinsame Methoden.

Methoden der ReaderKlasse (und aller ihrer Nachkommenklassen):

Methoden Beschreibung
int read()
Liest einen charaus dem Stream
int read(char[] buffer)
Liest ein charArray aus dem Stream
long skip(long n)
Sprünge n charsim Stream (liest und verwirft sie)
boolean ready()
Überprüft, ob noch etwas im Stream vorhanden ist
void close()
Schließt den Stream

Die Methoden sind denen der InputStreamKlasse sehr ähnlich, es gibt jedoch leichte Unterschiede.

int read()Methode

Diese Methode liest einen charaus dem Stream und gibt ihn zurück. Der charTyp 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 Readerauf 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 ReaderObjekts überspringen. Es funktioniert genauso wie die analoge Methode der InputStreamKlasse. 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
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileReader reader = new FileReader(src);
FileWriter writer = new FileWriter(dest))
{
   char[] buffer = new char[65536]; // 128Kb
   while (reader.ready())
   {
      int real = reader.read(buffer);
      writer.write(buffer, 0, real);
   }
}



Readerzum Lesen aus einer Datei
Writerzum 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