michael.eichberg@dhbw.de, Raum 149B
1.0
Verschiedene Quellen/Senken für die Daten
Tastatureingabe („Standard I/O“)
Datei(en) auf dem lokalen Rechner („File I/O“)
Datei(en)/Prozess(e) im Netzwerk („Network I/O“)
Hauptspeicher („Memory I/O“)
Um einheitlich Daten auf unterschiedliche „Datenbehälter“ ein-/auszugeben, verwendet Java das Konzept der „Streams“ (Ströme): Eingabe- und Ausgabeströme (E/A, I/O)
Datenströme als eine Abstraktion von I/O „Geräten“
Verstecken Details über die Implementierung / Funktionsweise der einzelnen I/O-Geräte vor dem Java Programmierer
(D. h. ob die Eingabe zum Beispiel eine Tastatur, Datei, anderes Programm, Netz, Hauptspeicher, … ist.)
Stellen Java-Programmen einheitliche Schnittstellen zum Lesen bzw. Schreiben von Daten zur Verfügung.
Lesen von Daten
Um Daten von einer externen Datenquelle zu lesen, öffnet ein Java-Programm einen Eingabestrom und liest die Daten seriell (nacheinander) ein:
Schreiben von Daten
Um Daten in eine externe Senke zu schreiben, öffnet ein Java-Programm einen Ausgabestrom und schreibt die Daten seriell.
lesen / schreiben char
(16-bit Unicode Zeichensatz).
java.io.Reader
/java.io.Writer
stellen die Schnittstelle und eine partielle Implementierung von Zeichenströmen zur Verfügung.
Subklassen von Reader
/Writer
fügen neues Verhalten hinzu bzw. ändern dieses.
lesen / schreiben byte
s (8-bit).
Werden zum Lesen bzw. Schreiben von Binärdaten, z.B. Bildern, benutzt.
java.io.InputStream
/java.io.OutputStream
: gemeinsame Schnittstelle und partielle Implementierung für alle Ströme zum Lesen bzw. Schreiben von Bytes.
Alle anderen Byteströme sind Unterklassen davon.
Daten werden direkt von „physikalischer“ Datenquelle gelesen bzw. auf „physikalische“ Datensenke geschrieben
Daten werden von anderen Strömen gelesen bzw. auf andere Ströme geschrieben
Daten werden nach dem Lesen bzw. vor dem Schreiben gefiltert, gepuffert, bearbeitet, usw.
Öffne einen Strom
Ströme werden beim Erzeugen automatisch geöffnet
Lese Daten, solange nötig und es noch Daten gibt
Schließe den Strom
Beim Beenden des Lesens bzw. Schreibens ist der Strom durch close()
zu schließen.
Öffne einen Strom
Solange es noch Daten gibt, schreibe Daten
Schließe den Strom mit close()
.
1public interface InputStream {
2public int read()
3public int read(byte[] bbuf)
4public int read(byte[] bbuf, int offset, int len)
5}
1public interface Reader {
2public int read()
3public int read(char[] cbuf)
4public int read(char[] cbuf, int offset, int len)
5}
1public interface OutputStream {
2public int write(int b)
3public int write(byte[] bbuf)
4public int write(byte[] bbuf, int offset, int len)
5}
1public interface Writer {
2public int write(int c)
3public int write(char[] cbuf)
4public int write(char[] cbuf, int offset, int len)
5}
Dateiströme sind Ein-/Ausgabe-Ströme, deren Quellen/Senken Dateien im Dateisystem sind:
FileReader
/ FileWriter
für Lesen / Schreiben von Zeichen aus/in Dateien
FileInputStream
/ FileOutputStream
für Lesen / Schreiben von Bytes von/in Dateien
Ein Dateistrom kann erzeugt werden, indem man die Quelle- bzw. Senke-Datei durch eines der folgenden Objekte als Parameter des Strom-Konstruktors übergibt:
Dateiname (String
)
Datei-Objekt (java.io.File
)
Dateibeschreibung (java.io.FileDescriptor
)
Die Klasse java.nio.file.Files
bietet weitere Methoden (z. B. newInputStream(...)
, newBufferedWriter(...)
) zum Lesen und Schreiben von Dateien als Streams an.
Einfaches (ineffizientes) Beispiel
1void print(String fileName) throws IOException {
2try (FileReader in = new FileReader(fileName)) {
3int b;
4while ((b = in.read()) != -1) System.out.print(b);
5}
6}
Ein Prozess-Strom enthält einen anderen (Daten- oder Prozess-)Strom
Dieser dient als Quelle bzw. Senke.
Prozess-Ströme ändern Daten oder bieten Funktionalität:
Zwischenspeichern (Puffern) von Daten
Zählen der gelesenen/geschriebenen Zeilen
Konvertierung zwischen Byte und Zeichen
Kompression, …
Pufferströme
Ein Pufferstrom (z. B. BufferedInputStream
oder BufferedOutputStream
) kapselt einen anderen Datenstrom und einen internen Puffer
Beim ersten Lesen wird der Puffer vollständig gefüllt
Weitere Lese-Operationen liefern Bytes vom Puffer zurück, ohne vom unterliegenden Strom tatsächlich zu lesen.
Bei leerem Puffer wird erneut vom unterliegenden Strom gelesen
Beim Schreiben werden die Daten zuerst in dem internen Puffer gespeichert, bevor sie in den unterliegenden Strom geschrieben werden.
Nur wenn der Puffer voll ist, wird auf den unterliegenden Strom geschrieben
Sowie bei explizitem Aufruf der Methode flush()
oder close()
.
Einfaches Beispiel
1try(
2FileOutputStream fos = new FileOutputStream("Test.tmp");
3BufferedOutputStream bos = new BufferedOutputStream(fos);
4DataOutputStream out = new DataOutputStream(bos)
5) {
6out.writeInt(9);
7out.writeDouble(Math.PI);
8out.writeBoolean(true);
9}
1$ hexdump Test.tmp
20000000 0000 0900 0940 fb21 4454 182d 0001
Ströme können ineinander verschachtelt werden.
Abstraktionsebenen, bei denen unterliegende „primitive“ Ströme von umschließenden („höheren“, komfortableren) Strömen benutzt werden („Prozessströme“).
1// erzeugt gepufferten, komprimierenden Dateiausgabestrom
2OutputStream out = new FileOutputStream(<filename>);
3var bout = new BufferedOutputStream(out);
4var zout = new ZipOutputStream(bout);
5// … mehr Eigenschaften koennen dynamisch hinzugefuegt werden
6// Die Stromeigenschaften sind unsichtbar fuer Klienten
Die Technik, mit der erreicht wird, dass Ströme beliebig zur Laufzeit kombiniert werden können, ist nicht nur im Kontext von Strömen von Interesse. Es handelt sich um eine generelle Technik, um Objekte dynamisch mit Features zu erweiterten.
In der Softwaretechnik werden solche Techniken in der Form so genannter Design Patterns (Entwurfsmuster) dokumentiert; d. h. wiederverwendbare, dokumentierte Designideen.
Die oben genannte Technik bei Streams ist als „Decorator Pattern“ bekannt.
Datei lesen und ausgeben
Schreiben Sie ein Programm, dass eine Textdatei liest und die Zeilen in der Konsole ausgibt. Schreiben Sie vor jede Zeile die Zeilennummer.
Stream
s und I/Ojava.nio.file.Files
Neben den traditionellen I/O-Klassen (seit Java 1.X) gibt es auch die Möglichkeit Dateien als Streams zu lesen und zu schreiben (java.nio.file.Files
).
1package java.nio.file;
23
public class Files {
4/** Read all lines from a file as a Stream. */
5static Stream<String> lines(Path path)
67
/** Read all lines from a file as a Stream. */
8static Stream<String> lines(Path path, Charset cs)
910
// ...
11}
Streamverarbeitung von Dateien
Schreiben Sie ein Programm, das eine Textdatei liest und die Zeilen in der Konsole ausgibt. Jeder Zeile soll weiterhin die Zeilennummer vorangestellt werden. Verwenden Sie dazu die Klasse Files und die Methode lines.
Durchsuchen von Dateien
Schreiben Sie ein Programm (Sie können die JShell benutzen), dass alle Textdateien (z. B. *.rxt, *.md oder *.java) eines Verzeichnisses in Hinblick auf das Vorkommen eines bestimmten Wortes (z. B. Java) durchsucht. Geben Sie den Namen der Datei und eine Zeilennummer aus, in der das Wort vorkommt. Parallelisieren Sie die Suche wenn möglich.
Relevante API: Files.walk
, Files.isRegularFile
, Files.lines
, Stream.filter
, Stream.map
, Stream.findAny
, Optional.isPresent
, Optional.get
, Optional.empty