Am 14. September 2017 haben wir eine überarbeitete Fassung unserer Datenschutzrichtlinie veröffentlicht. Wenn Sie video2brain.com weiterhin nutzen, erklären Sie sich mit diesem überarbeiteten Dokument einverstanden. Bitte lesen Sie es deshalb sorgfältig durch.

Java 7 Grundkurs

Datenströme

LinkedIn Learning kostenlos und unverbindlich testen!

Jetzt testen Alle Abonnements anzeigen
Im java.io-Package finden Sie die Klassen, die Sie für die Verarbeitung von Datenströmen benötigen. Der Trainer zeigt Ihnen, wie dieses Baukastenprinzip aufgebaut ist und wie Sie Ihre gewünschte Funktionalität daraus aufbauen können.
09:54

Transkript

Für die Verarbeitung von Datenströmen stehen in der Java-Standardbibliothek eine Vielzahl verschiedener Klassen zur Verfügung. Die befinden sich alle im Package "java.io". Wir können jetzt hier sehen, es sind schon eine ganz Menge Klassen. Die meisten davon lassen sich in eine von vier Gruppen einteilen und zwar nach der Richtung, in der die Daten fließen und nach der Art der Daten. Datenströme haben immer eine Quelle und ein Ziel. Wir können also unterscheiden zwischen Daten, die in das Programm hineinkommen und Daten, die aus dem Programm hinausgehen. Bei der Art der Daten können wir unterscheiden zwischen Binärdaten, also Byte-Streams, z.B. ein JPEG-Bild, und Textdaten, die aus Text bestehen. Für jede der vier Kombinationsmöglichkeiten gibt es nun eine entsprechende Basisklasse, die die jeweilige Grundfunktionalität anbietet. Für Binärdaten haben wir "InputStreams" und "OutputStreams" und für Textdaten "Reader" und "Writer". Und Grundfunktionalität heißt hier auch wirklich minimale Grundfunktionalität. Wir würden z.B. InputStream anschauen, dann haben wir hier zum Lesen im Wesentlichen die Möglichkeit, ein einzelnes Zeichen zu lesen, also ein einzelnes Byte, oder ein Byte-Array zu befüllen. Dann können wir mit "Skip" noch einzelne Zeichen überspringen und mit "Close" den InputStream schließen. Bei den anderen drei Klassen sieht es ähnlich aus. Deshalb gibt es zu diesen vier Klassen jeweils eine Vielzahl spezialisierter Unterklassen. Das ganze Paket ist dabei nach dem Baukastenprinzip aufgebaut. Es wird hier das objektorientierte Entwurfsprinzip "ein Objekt, eine Verantwortlichkeit" umgesetzt. Das heißt, jede der Klassen kann genau eine Sache, die aber gut. Wenn wir dann eine komplexere Funktionalität benötigen, dann können wir mehrere dieser Objekte zusammenstecken, um die gewünschte Funktionalität zu erhalten. Ich möchte das an einem Beispiel verdeutlichen. Nehmen wir an, wir möchten diverse Daten in eine Binärdatei schreiben. Dafür gibt es  die Klasse "FileOutputStream", eine Unterklasse von "OutputStream", die in Dateien schreiben kann. Sie weiß also, wie man Dateien öffnet, und dort Daten hineinbekommt. Das ist aber eben auch schon alles, was ich mit einem FileOutputStream machen kann: ein einzelnes Byte oder ein Byte-Array in eine Datei schreiben. Ich müsste meine Daten also erstmal alle konvertieren, bevor ich sie dann schreiben kann. Auch einen internen Puffer gibt es im FileOutputStream nicht. Das heißt, jeder Schreibvorgang wird tatsächlich direkt in die Datei ausgeführt. Wenn ich also sehr viele Schreibvorgänge jeweils mit sehr kleinen Datenmengen durchführe, dann wird das ganze sehr langsam. Da kommt nun der nächste Baustein ins Spiel, nämlich der "BufferedOutputStream". Wie der Name schon andeutet, ist es die Spezialität dieser Klasse, einen internen Puffer vorzuhalten. Wir können also diese beiden zusammenstecken, dann führen wir unsere Schreibvorgänge in dem BufferedOutputStream aus, dieser wiederum füllt erstmal seinen internen Puffer. Wenn dieser voll ist, dann schickt er das Ganze in einem Rutsch an den FileOutputStream, der das dann in einem Rutsch rausschreiben kann. Feine Sache, das Geschwindigkeitsproblem haben wir gelöst. Aber wir müssen unsere Daten immer noch selbst konvertieren, denn so etwas kann der BufferedOutputStream natürlich nicht. Greifen wir also nochmal in die Kiste und holen einen weiteren Baustein heraus, diesmal den "DataOutputStream". Die Spezialität dieser Klasse ist es, viele verschiedene Methoden anzubieten, mit denen ich viele verschiedene Datentypen schreiben kann. Der DataOutputStream konvertiert diese dann in einen Bytestrom, den er am anderen Ende wieder ausspuckt. Wenn wir diese drei Bausteine nun also hintereinanderstecken, dann können wir viele verschiedene Datentypen gepuffert in eine Binärdatei schreiben. Um aus der Datei zu lesen, läuft das ganze dann analog ab. Dafür gibt es den FileInputStream, der aus der Datei lesen kann, aber nur mit sehr einfachen Methoden. Dann haben wir den BufferedInputStream, der bei einer einzelnen Leseanforderung gleich mal seinen internen Puffer füllt, um die folgenden Leseanforderungen dann erstmal aus diesem bestreiten zu können, und schließlich einen DataInputStream, der uns die Möglichkeit bietet, gleich die Konvertierung in verschiedene Datentypen vornehmen zu lassen. Das Ganze habe ich Ihnen hier mal als Beispielprojekt vorbereitet. Am Quelltext wird die Sache vielleicht noch etwas deutlicher. In meiner Klasse StreamsDemo habe ich hier die main-Methode. Darin erstelle ich mir eine Stringvariable, die den Dateinamen vorhält. Nun rufe ich nacheinander zwei Methoden auf, die erste heißt "schreiben", nimmt den Dateinamen und schreibt ein paar Daten dort rein, und die zweite heißt "lesen", nimmt denselben Dateinamen, holt dort die Daten wieder raus und zeigt sie an. Schauen wir uns zuerst das "schreiben" an. Wir deklarieren uns hier eine Variable "out" vom Typ DataOutputStream. Dann erzeugen wir uns natürlich ein DataOutputStream-Objekt. Im Konstruktor bekommt dieser DataOutputStream als Parameter einen an dieser Stelle erzeugten BufferedOuputStream, welcher wiederum ebenfalls einen Parameter in seinem Konstruktor bekommt, nämlich einen FileOutputStream, und dieser wiederum nimmt in seinem Konstruktor einen Parameter, nämlich den Dateinamen. Auf diese Weise verkette ich also diese drei Objekte miteinander. Der DataOutputStream schickt seine Daten an den BufferedOutputStream, dieser schickt seine Daten an den FileOutputStream, und dieser schreibt die Daten in die Datei. Schauen wir uns diese Konstruktoren mal in der API-Doc an. Ich habe die Klasse DataOutputStream, also das, was ganz links sitzt. Hier gibt es einen Konstruktor, nämlich diesen hier, und der nimmt einfach irgendeinen OutputStream. Es ist ihm also ziemlich egal, welchen Baustein ich dahinterstelle, Hauptsache es ist ein OutputStream, an den er seine Daten loswerden kann. Das Gleiche gilt für BufferedOutputStream, der zweite in unserer Reihe. Auch dieser möchte im Konstruktor einfach einen OutputStream haben. Wir können uns also wirklich die Klassen auswählen, deren Funktionalität wir benötigen und sie in der gewünschten Reihenfolge zusammenstecken. Wenn irgendwann mal der Schreibvorgang beendet sein sollte, muss ja irgendwann die Ressource wieder geschlossen werden, also in unserem Fall die Datei. Dafür wird bereits in der Basisklasse OutputStream die Methode "close()" definiert. Das heißt, alle drei dieser Objekte hier haben eine "close()"-Methode, die gerne aufgerufen werden möchte, wenn wir fertig sind. Glücklicherweise müssen wir aber nur die erste aufrufen, nämlich die vom DataOutputStream. Der reicht nämlich auch den "close()"-Aufruf an den nächsten weiter, der den dann wiederum an den FileOutputStream weiterreicht, der die Datei schließt. Damit wäre dann also alles aufgeräumt. Nun möchten wir natürlich, dass die Datei auf jeden Fall geschlossen wird, auch wenn es zwischendurch, beim Schreiben z.B., irgendwelche "Exceptions"geben sollte. Dabei hilft uns dieses Konstrukt hier, das "try-with-resources()" genannt wird. Die Ressource, oder die Ressourcen, die ich nach dem "try" in runden Klammern deklariere, werden beim Verlassen des try-Blocks automatisch geschlossen, indem jeweils "close()" aufgerufen wird. Durch diese automatische Ressourcenverwaltung müssen wir uns um das Schließen also selbst überhaupt nicht mehr kümmern. Stattdessen können wir uns nun direkt mit dem Schreiben unserer Daten beschäftigen. Ich habe hier einen String deklariert, ein "int" und ein "double", und schreibe sie nacheinander in die Datei raus. Mit "writeUTF()" schreibe ich den String, mit "writeInt()" das Alter und mit "writeDouble()" die Größe. Beim Erstellen des FileOutputStreams könnte eine FileNotFoundException auftreten, und bei den Schreibvorgängen könnten IOExceptions auftreten. Die müssen wir hier natürlich fangen und adäquat behandeln. Damit kommen wir zur lesen()-Methode. Auch die nimmt den Dateinamen als String und macht jetzt das, was wir in der Grafik gesehen haben, baut also auch wieder eine Kette aus diesen drei Objekten auf. Ein DataInputStream wird erzeugt, den wir hier "In" nennen. Der bekommt im Konstruktor einen BufferedInputStream, der wiederum im Konstruktor als Parameter einen FileInputStream bekommt, dem wir den Dateinamen übergeben. Auch hier lassen wir uns die Ressourcen wieder automatisch verwalten. Nun lesen wir die Daten in der gleichen Reihenfolge aus, in der wir sie vorher hineingeschrieben haben. Das heißt, zuerst holen wir den String mit "readUTF()", dann die ganze Zahl mit "readInt()", und schließlich den double-Wert mit "readDouble()", und merken uns diese Werte in den entsprechenden Variablen. Danach geben wir das ganze noch aus, um zu sehen, ob es auch wirklich funktioniert hat. Auch hier können wieder FileNotFoundException und IOException auftreten, die müssen wir natürlich fangen. Jetzt führen wir das Programm natürlich mal aus. Ging recht fix, hier unten sehen wir auch schon die Ausgabe: Klaus ist 42 Jahre alt und 1,82m groß. Ja, das sind tatsächlich die Daten, die wir da oben eingegeben haben. Außerdem ist jetzt in unserem Projektverzeichnis eine Datei mit diesem Namen entstanden. Um das zu sehen, gehen wir mal hier links, wo unsere Projektstruktur ist, auf den zweiten Tab, der heißt "Files". Dann bekommen wir eine Sicht auf das Dateisystem. Hier sehen wir also, wie unser Projekt auf der Festplatte aussieht in unserem Sourceordner mit den Quelltextdateien, und hier haben wir unsere "Data.bin-Datei". Dann bekomme ich eine Warnung, dass das eine Binärdatei ist. Und in einem Texteditor sehen die ja immer ein bisschen komisch aus, aber wir sagen jetzt mal "OK". Dann bekommen wir noch eine Zeichensatz- warnung eventuell, sagen wir "Yes". Und hier sehen wir jetzt, wie diese Binärdaten in der Datei aussehen. Der String steht offensichtlich noch im Klartext drin, mit dem Rest könnten wir jetzt so ohne Weiteres nichts anfangen. Es ist halt eine Binärdatei. Ich schließe die mal wieder und schalte auch hier mal wieder auf das Projekt um. Sie haben nun also gesehen, wie wir diese Funktionalität hier in einer Anwendung umsetzen könnten. Bei Textdateien wäre das ganze dann im Prinzip analog, nur dass wir hier nicht FileOutputStream hätten, sondern "FileWriter". Diese Klasse würde "BufferedWriter" heißen. Und das hier wäre dann ein "PrintWriter". Statt dieser lustig benannten Methoden gibt es dort einfach eine Methode "print()" und eine Methode "println()", die dann jeweils für alle primitiven Datentypen überladen sind. Auch das zeige ich Ihnen noch in der API-Doc. Hier, Klasse PrintWriter, und dort haben wir dann hier: "print()", "print(double)", "print(float)" usw. Das heißt, wir rufen einfach nur die "print()"-Methode auf, übergeben irgendwas, und der PrintWriter, der konvertiert es dann schon. Möchten wir einen Zeilenvorschub am Ende, dann nehmen wir einfach noch ein "ln" hinten dran und rufen die Methoden "println()" mit dem entsprechenden Datentyp auf. Anders gesagt: es funktioniert genau so, wie Sie das von "System.out.println()" bereits kennen. In der Gegenrichtung haben wir dann hier den "FileReader". Dieses Teil heißt "BufferedReader". Und um Textdateien einzulesen, und darum geht es ja dann, ist der bereits recht komfortabel. Hier ist der "BufferedReader" in der API-Doc. Und wir sehen hier: es gibt eine Methode "ReadLine", die einfach eine ganze Zeile aus der Textdatei einliest und uns als String zurückgibt. Damit haben Sie nun gesehen, wie dieses Paket "java.io" aufgebaut ist, und wie Sie sich die gewünschte Funktionalität durch dieses Baukastenprinzip jeweils aus den Einzelteilen zusammenstellen können.

Java 7 Grundkurs

Machen Sie sich mit den Grundlagen der Java-Programmierung vertraut und lernen Sie die Syntax der Sprache sowie das Konzept der objektorientierten Softwareentwicklung kennen.

8 Std. 32 min (66 Videos)
Derzeit sind keine Feedbacks vorhanden...
 

Dieser Online-Kurs ist als Download und als Streaming-Video verfügbar. Die gute Nachricht: Sie müssen sich nicht entscheiden - sobald Sie das Training erwerben, erhalten Sie Zugang zu beiden Optionen!

Der Download ermöglicht Ihnen die Offline-Nutzung des Trainings und bietet die Vorteile einer benutzerfreundlichen Abspielumgebung. Wenn Sie an verschiedenen Computern arbeiten, oder nicht den ganzen Kurs auf einmal herunterladen möchten, loggen Sie sich auf dieser Seite ein, um alle Videos des Trainings als Streaming-Video anzusehen.

Wir hoffen, dass Sie viel Freude und Erfolg mit diesem Video-Training haben werden. Falls Sie irgendwelche Fragen haben, zögern Sie nicht uns zu kontaktieren!