Grundzüge der Informatik V12. Ein- und Ausgabe in Java Prof. Dr. Max Mühlhäuser FG Telekooperation TU-Darmstadt Agenda • • • • • • Warum Ein-/Ausgabe in Java? Ein-/Ausgabeströme und -oberklassen Zeichenströme und Byteströme Dateiströme, Filter, Puffer und PushBack Tokenizer Freier (random) Zugriff 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 2 Eingabe /Ausgabe (Input / Output, I/O) • Bislang wurden Eingabedaten fest im Quelltext codiert • Zusätzlich wurden die Werte als Argument bei Programmstart übergeben – java fakultaet 7 • Folgen dieser Einschränkungen: – Geringe Flexibilität der Programme und Algorithmen – Datenänderung erfolgt Programmänderung und Neuübersetzung – Ungeeignet für größere oder komplexere Daten – Keine Interaktionsmöglichkeit – Ergebnisse sind nicht speicherbar 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 3 Eingabe /Ausgabe (Input / Output, I/O) • Ziele von I/O: – Speicherung von Ergebnissen – Verarbeitung von gespeicherten Daten – Einbeziehen weiterer gespeicherter Medien, etwa Bilder • Woher stammen die Daten eines Programmes? – – – – – – – Codiert im Programmtext Parameter bei Programmstart Tastatureingabe („Standard I/O“) Datei(en) auf dem lokalen Rechner („File I/O“) Datei(en) im Netzwerk („Network I/O“) Ergebnisse / Eingaben eines anderen Programms („Piped I/O“) Hauptspeicher („Memory I/O“) 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 4 I/O-Ströme • Um Daten ein- / auszugeben, wird in Java das Konzept der „Streams“ (Ströme, Datenströme) eingeführt • Datenströme sind eine Abstraktion von Eingabe- bzw. Ausgabeverbindungen des Programms zu externen “Datenbehältern“ – Beispiele, s.o.: Tastatur, Datei, anderes Programm, Netz, Hauptsp., … – Grundlage ist Unix (in Java wesentlich erweitert), dort gilt: ein Datenbehälter sendet bzw. empfängt unstrukturierte Ströme von Bytes • Ströme stellen Java-Programmen einheitliche Schnittstellen zum Lesen bzw. Schreiben von Daten zur Verfügung • Ströme verstecken Details über die Implementierung u. Funktionsweise der einzelnen I/O Geräte vor dem Java Programmierer – Das Java Programm “spricht“ mit Java I/O Objekten • Man unterscheidet Eingabe- und Ausgabeströme 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 5 I/O-Ströme • Um Daten von einer externen Datenquelle zu lesen, öffnet ein JavaProgramm einen Eingabestrom und liest die Daten (nacheinander) ein: • Um Daten in eine externe Senke zu schreiben, öffnet ein JavaProgramm einen Ausgabestrom und schreibt die Daten seriell 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 6 Verschachtelung der Ströme • Das externe Medium kann ein anderer Datenstrom sein. • Ströme können ineinander verschachtelt sein, indem ein Strom einen anderen unterliegenden Strom einkapselt und ihn als Datenquelle bzw. Senke benutzt. BufferedReader StreamTokenizer File Reader Datenquelle, z.B. Datei • Sinn: Daten können vor dem Schreiben auf den unterliegenden Strom bzw. nach dem Lesen bearbeitet werden • ÆAbstraktionsebenen, bei denen unterliegende ‚primitive‘ Ströme von einkapselnden (‚höheren‘, komfortableren) Strömen benutzt werden („Prozeßströme“) 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 7 Kategorisierung der vordefinierten Ströme • java.io enthält eine Sammlung von Strom-Klassen • Zwei Kategorisierungen von Datenströmen: – Zeichenströme / Byteströme – Datensenkeströme / Prozeßströme Klassenhierarchie Klassenhierarchie der derZeichenströme Zeichenströme Klassenhierarchie Klassenhierarchie der derByteströme Byteströme Klassen Klassenfür für Datensenkeströme Datensenkeströme Klassen Klassenfür für Prozeßströme Prozeßströme 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 8 Kategorisierung der vordefinierten Ströme • Zeichenströme lesen / schreiben char (16-bit Unicode) • Byteströme lesen / schreiben byte (8 bit) • Datensenkeströme – Daten werden direkt von konkreter Datenquelle gelesen bzw. auf konkrete Datensenke geschrieben • Prozeßströme – Daten werden von anderen Strömen gelesen bzw. auf andere Ströme geschrieben – Daten werden nach dem Lesen, bzw. vor dem Schreiben bearbeitet 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 9 I/O-Ströme • Die Algorithmen zum Lesen bzw. Schreiben von Daten haben unabhängig von Datentyp und Quelle bzw. Senke immer die gleiche Form: • Lesen • öffne einen Strom • solange es noch Daten gibt lese Daten • schließe den Strom • Schreiben • öffne einen Strom • solange es noch Daten gibt schreibe Daten • schließe den Strom 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 10 Eingabe I/O-Oberklassen Reader und InputStream definieren ähnliche Schnittstellen, aber für verschiedene Datentypen: InputStream public int read() public int read(byte[] bbuf) public int read(byte[] bbuf, int offset, int len) Reader public int read() public int read(char[] cbuf) public int read(char[] cbuf, int offset, int len) 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 11 Ausgabe I/O-Oberklassen • Writer und OutputStream definieren ähnliche Schnittstellen, parallel zu Reader und InputStream: OutputStream public int write(int b) public int write(byte[] bbuf) public int write(byte[] bbuf, int offset, int len) Writer public int write(int c) public int write(char[] cbuf) public int write(char[] cbuf, int offset, int len) 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 12 I/O-Oberklassen • Ströme werden beim Erzeugen automatisch geöffnet • Bei Beenden des Lesens bzw. Schreibens sollte der Strom durch close() geschlossen werden • Der Java Garbage Collector löscht notfalls einen Strom, auf den nicht mehr verwiesen wird 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 13 Zeichenströme • Zeichenströme ermöglichen das Lesen bzw. Schreiben von 16-Bit-Zeichen • java.io.Reader/java.io.Writer stellen eine Schnittstelle und eine partielle Implementierung für Eingabebzw. Ausgabe-Ströme zur Verfügung • Unterklassen von Reader/Writer konkretisieren bzw. spezialisieren die Implementierung in Reader/Writer • Zeichenströme können alle Zeichen in der UnicodeZeichenmenge lesen und schreiben. Dagegen sind Byteströme beschränkt auf ISO-Latin-1 Bytes • Die meisten Programme benutzen Reader/Writer, um Informationen zu lesen bzw. zu schreiben 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 14 Hierarchie der Zeichenströme Reader StringReader InputStreamReader InputStreamReader FilterReader FilterReader BufferedReader BufferedReader FileReader PushbackReader PushbackReader LineNumberReader LineNumberReader Die Klassen in schattierten Kästchen sind Prozeßströme 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 15 Hierarchie der Zeichenströme Writer StringWriter OutputStreamWriter OutputStreamWriter FileWriter FilterWriter FilterWriter BufferedWriter BufferedWriter PrintWriter PrintWriter Die Klassen in schattierten Kästchen sind Prozeßströme 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 16 Byteströme • Byteströme stellen Funktionalität zum Lesen bzw. Schreiben von Bytes zur Verfügung • Normalerweise werden Byteströme zum Lesen bzw. Schreiben von Binärdaten, z.B. Bildern, benutzt • java.io.InputStream/java.io.OutputStream stellen die gemeinsame Schnittstelle und partielle Implementierung für alle Ströme zum Lesen bzw. Schreiben von Bytes zur Verfügung • Alle Byteströme sind Unterklassen von InputStream/OutputStream 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 17 Hierarchie der Byteströme InputStream FileInputStream FilterInputStream FilterInputStream BufferedInputStream BufferedInputStream DataInputStream DataInputStream LineNumberInputStream LineNumberInputStream PushbackInputStream PushbackInputStream Die Klassen in schattierten Kästchen sind Prozeßströme 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 18 Hierarchie der Byteströme OutputStream FileOutputStream BufferedOutputStream BufferedOutputStream FilterOutputStream FilterOutputStream DataOutputStream DataOutputStream PrintStream PrintStream Die Klassen in schattierten Kästchen sind Prozeßströme 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 19 Nutzung der Klasse java.io.InputStream • Objekte der Unterklassen von InputStream lesen Bytes aus einer bestimmten Quelle (Datei, Netzwerk,...) • Die wesentlichsten Methoden der Klasse sind: – public abstract int read() throws IOException • Liefert das nächste Byte im Strom (nur als 0-255) • -1 falls keine weiteren Bytes vorhanden sind (End of File, „EOF“) • IOException bei allen anderen Problemen – Z.B. Strom bereits geschlossen, Netzwerkverbindung verloren, ... 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 20 Nutzung der Klasse java.io.InputStream • public abstract int read() Datenquelle Programm InputStream Datenquelle Programm InputStream Datenquelle -1 Programm InputStream 09.01.2003 © MM ... IOException GDI-1.12 - Ein- und Ausgabe in Java Legende Gelesene Bytes Nächstes Byte EOF geöffneter Strom geschlossener Strom 21 Nutzung der Klasse java.io.InputStream • public int read(byte[] b) throws IOException, NullPointerException – liest eine Anzahl von Bytes und speichert sie in dem Byte-Array b. Ruft read() wiederholt auf – gibt Anzahl der gelesenen Bytes zurück bzw. -1, wenn kein Byte gelesen werden konnte wegen EOF – NullPointerException, wenn b == null – IOException, wenn das erste Byte nicht gelesen werden konnte aus irgendeinem anderen Grund als EOF – I/O Ausnahmen während des Lesens von anderen Bytes werden abgefangen (behandelt anstelle sie weiterzugeben) und die Anzahl der Bytes soweit gelesen zurückgegeben 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 22 Nutzung der Klasse java.io.InputStream Programm Datenquelle public int read(byte[] b) InputStream Datenquelle b 3 Programm InputStream b -1 Programm Datenquelle InputStream b IOException Programm Datenquelle InputStream 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java b 2 23 Nutzung der Klasse java.io.InputStream • public long skip(long n) throws IOException – versucht n Bytes von dem Eingabestrom zu überspringen – Zurückgegeben wird die Anzahl übersprungener Bytes – die Anzahl der tatsächlich übersprungenen Bytes kann kleiner als n sein: • EOF wurde erreicht bevor n Bytes übersprungen wurden • IOException, wenn das erste Byte nicht gelesen werden konnte aus irgendeinem anderen Grund als EOF • public int available() throws IOException – Gibt die Anzahl noch möglicher read()-Operationen aus • public int close() throws IOException – Schließt den Strom. Aus einem geschlossenen Strom kann nicht mehr gelesen werden. 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 24 Nutzung der Klasse java.io.OutputStream • OutputStream implementiert Byteströme, die AusgabeBytes erwarten und sie zu einer Senke schicken • public abstract void write(int b) throws IOException – schreibt ein Byte (8 low-order Bits von b) auf die Senke • public void write(byte[] b) throws IOException, NullPointerException – schreibt alle Bytes aus b sequentiell auf den Strom. Ruft write(int) wiederholt auf. • void write (byte[] b, int off, int len) throws IOException, NullPointerException, IndexOutOfBoundException – Schreibt das gewählte Arraysegment durch mehrfaches write(int) auf den Strom 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 25 Nutzung der Klasse java.io.OutputStream • public void flush() throws IOException – Alle vor dem Aufruf von flush() geschriebenen Bytes, die eventuell innerhalb des Stroms gepuffert sind, werden sofort auf die Senke des Stroms geschrieben – Da OutputStream selbst die Daten sofort auf die Senke schreibt, hat der Aufruf von flush() auf einen OutputStream keine Wirkung. Das kann für Unterklassen von OutputStream anders sein. • Für weitere Methoden siehe die Beschreibung in der Java API Documentation 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 26 Datensenkeströme: Überblick Senke Typ Zeichenströme Byteströme Hauptspeicher StringReader StringWriter StringBufferInputStream Datei FileReader FileWriter FileInputStream FileOutputStream StringBufferInputStream, StringReader und StringWriter ermöglichen das Lesen von bzw. Schreiben auf Strings im Hauptspeicher 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 27 Dateiströme • Dateiströme stellen Eingabe-/Ausgabe-Ströme zur Verfügung, deren Quellen/Senken Dateien im Dateisystem sind: – FileReader / FileWriter und – FileInputStream / FileOutputStream • Ein Dateistrom kann erzeugt werden, indem man die Quelle- bzw. Senke-Datei durch eines der folgenden Objekte als Parameter des Strom-Konstruktors eingibt: – Dateiname (String) – Datei-Objekt (java.io.File) – Dateibeschreibung (java.io.FileDescriptor) 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 28 Die Klasse FileInputStream • FileInputStream stellt einen Byte-Eingabe-Datenstrom dar, dessen Quelle eine Datei im Dateisystem ist • public FileInputStream(String path) throws SecurityException, FileNotFoundException – Erzeugt einen neuen Dateilesestrom durch Öffnen der benannten Datei – FileNotFoundException, falls die Datei nicht existiert – SecurityException, falls keine Leseberechtigung vorliegt • Die Beschreibung passt auch auf die Varianten mit Parameter java.io.File oder java.io.FileDescriptor 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 29 Die Klasse FileInputStream Beispiel: Ausgeben eines Dateiinhalts auf den Bildschirm import java.io.FileInputStream; import java.io.IOException; public class PrintFileFile { public static void main(String[] args) throws IOException { FileInputStream in = new FileInputStream(args[0]); int b; while ((b = in.read()) != -1) System.out.print(b); in.close(); } } 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 30 Die Klasse FileOutputStream • FileOutputStream stellt einen Ausgabe-Strom dar, der in eine Datei schreibt • Beispiel: Die Zahlen von 1 bis 10 in eine Datei schreiben: public void writeOneToTen(String filename) throws IOException { FileOutputStream out = new FileOutputStream(filename); for (int i = 1; i <= 10; i++) out.write(i); out.close(); } 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 31 Prozeß-Ströme Prozeß-Ströme sind Datenströme, die zusätzliche Operationen auf den Daten, die von einem unterliegenden Datenstrom gelesen, bzw. geschrieben werden, vornehmen: • Zwischenspeichern (Puffern) von Daten während des Schreibens/Lesens auf/von dem Ursprungs-Datenstrom. • Zählen der gelesenen/geschriebenen Zeilen • Konvertierung zwischen Byte und Zeichen • ... Prozeß-Datenstrom Originaler Datenstrom 09.01.2003 © MM ... Datensenke GDI-1.12 - Ein- und Ausgabe in Java Datenquelle 32 Prozeß-Ströme Prozeß Zeichenströme Byteströme Filterung FilterReader, FilterWriter FilterInputStream, FilterOutputStream Pufferung BufferedReader, BufferedWriter BufferedInputStream, BufferedInputStream Byte / Char konvertieren InputStreamReader, OutputStreamWriter Zeile zählen LineNumberReader LineNumberInputStream DataInputStream, DataOutputStream Lesen von Datentypen Zurücklegen PushbackReader PushbackInputStream Drucken PrintWriter PrintStream 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 33 FilterInput-/FilterOutputStream • Enthält einen anderen Strom, der als Datenquelle bzw. Datensenke benutzt wird • Daten werden möglicherweise transformiert, oder zusätzliche Funktionalität wird zur Verfügung gestellt • FilterInputStream und FilterOutputStream sind Oberklassen aller Filter-Ströme • Unterklassen von InputStream / OutputStream • Überschreiben alle Methoden der jeweiligen Oberklassen, so dass die Aufrufe lediglich an den unterliegenden Strom weitergegeben werden • Konkrete Filter-Ströme (Unterklassen) spezialisieren diese Funktionalität 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 34 Die Klasse PrintStream • Unterklasse von FilterOutputStream • Erweitert einen anderen Ausgabestrom um die Ausgabe einer String-Darstellung verschieden-artiger Daten • PrintStream löst nie Ausnahmen aus • Stattdessen wird eine interne Markierung gesetzt, die mit der Methode checkError() abgefragt werden kann 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 35 Die Klasse PrintStream Wichtigste Methoden: • public void print(X x) • Ausgabe von x auf dem PrintStream • public void println(X x) • Ausgabe von x auf dem PrintStream, gefolgt von einem Zeilenwechsel • Dabei steht X für... • jeden primitiven Typ (int, char, long, ...) • String • sowie Object • Standarddarstellung ist nicht „lesbar“ 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 36 Standard I/O Im Paket java.lang gibt es die Klasse System mit den folgenden Konstanten (Klassenvariablen): System.in System.out System.err Standardeingabe (Tastatur) Standardausgabe (Bildschirm) Fehlermeldungen (Bildschirm) • System.in ist ein Objekt vom Typ InputStream – stellt read() zum byteweisen Lesen von Bytes von der Tastatur zur Verfügung • System.out, System.err sind vom Typ PrintStream – bieten print() und println() zur Ausgabe von Daten an 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 37 Standard I/O Beispiel: Lesen eines Zeichens von der Tastatur import java.io.IOException; public class StandardIOExample { public static void main(String[] args) { System.out.print(“>“); try { char c = (char) System.in.read(); System.out.println(c); } catch (IOException e) { System.err.println(e.getMessage()); } Bemerkung: Exceptions werden hier } durch Ausgabe der Fehlermeldung „behandelt“ } 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 38 Standard I/O System.in System.out System.err Standardeingabe (Tastatur) Standardausgabe (Bildschirm) Fehlermeldungen (Bildschirm) • Beim Starten von Java sind alle drei Ströme geöffnet • Die Ausgabe kann vom Programmierer umgeleitet werden durch Nutzung folgender Methoden von java.lang.System: public static void setIn(InputStream in) public static void setOut(PrintStream out) public static void setErr(PrintStream err) 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 39 Pufferströme • Ein Datenstrom mit Pufferung kapselt einen anderen Ursprungs-Datenstrom und einen internen Puffer • Beim Schreiben werden die Daten zuerst in dem internen Puffer gespeichert • Nur wenn der Puffer voll ist, wird auf den unterliegenden Strom geschrieben • Beim ersten Lesen wird der Puffer vollständig gefüllt. Weitere Lese-Operationen auf dem Strom liefern Bytes vom Puffer zurück, ohne vom unterliegenden Strom tatsächlich zu lesen 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 40 Pufferströme • BufferedInputStream und BufferedOutputStream bieten die Standard-Schnittstelle und Implementierung für Byteströme mit Pufferung an – Sie sind Unterklassen von FilterInputStream bzw. FilterOutputStream • BufferedReader und BufferedWriter bieten die Standard-Schnittstelle und Implementierung für Byte-Ströme mit Pufferung an – Sie sind Unterklassen von Reader bzw. Writer. 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 41 Pufferströme • BufferedOutputStream erweitert ByteAusgabeströme um die Fähigkeit, Daten zu puffern • Bytes können geschrieben werden, ohne für jedes Byte einen Zugriff auf dem unterliegenden Strom starten zu müssen • Beim Aufruf von einer Schreibe-Operation werden die Daten erst auf dem internen Puffer gespeichert • Daten werden auf den unterliegenden Strom geschrieben, wenn: die Kapazität des internen Puffers erschöpft ist, • der Strom geschlossen wird durch close() • flush() explizit aufgerufen wird • 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 42 Puffer-Ströme: BufferedOutputStream Datensenke Buffered OutputStream Programm b b write bbuf bbuf write write bbuf bbuf write bbuf bbuf flush 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 43 Pufferströme • BufferedInputStream erweitert Byte-Eingabeströme mit Pufferung der Daten • Bytes können von einem BufferedInputStream gelesen werden, ohne für jedes zu lesendes Byte einen Zugriff auf dem unterliegenden Stream starten zu müssen • Bei der ersten Lese-Operation werden so viele Daten von dem unterliegenden Strom gelesen, bis der Puffer voll ist • Folgende Lese-Operationen lesen die Daten vom Puffer und nicht vom System 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 44 Pufferströme: Beispiel import java.io.FileInputStream; import java.io.BufferedInputStream; import java.io.IOException; public class BufferDemo { public BufferDemo(String filename) { for (int i=1; i<65536; i = i * 2) testBuffer(filename, i); } // testBuffer auf naechster Folie! public static void main(String[] args) { BufferDemo bufferDemo = new BufferDemo(args[0]); } } 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 45 Pufferströme: Beispiel public void testBuffer(String fname, int bufferSize) { try { FileInputStream fis = new FileInputStream(fname); BufferInputStream bis = new BufferedInputStream(fis, bufferSize); int i; // read byte long timeNow = System.currentTimeMillis(); while ((i = bis.read()) != -1) ; // do nothing here... bis.close(); System.out.println("Size: " +bufferSize +",time:“ +(System.currentTimeMillis()-timeNow)); } catch(IOException e) { System.err.println(e.getMessage()); } } 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 46 Pufferströme: Beispiel • Ergebnis für eine 650kB-Datei (x=Puffergröße, y=Zeit [ms]) 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 47 BufferedReader Reader BufferedReader Reader() read() BufferedReader(Reader in) BufferedReader(Reader in, int size) readLine() LineNumberReader LineNumberReader(Reader in) LineNumberReader(Reader in, int size) getLineNumber() 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 48 Pushback-Ströme • Ein Pushback-Strom kapselt einen anderen Ursprungsdatenstrom • Ein vom unterliegenden Datenstrom gelesenes Datum (Byte bei PushbackInputStream bzw. char bei PushbackReader) kann „zurückgelegt“ werden („push back“ oder „unread“) • Die nächste read() Operation an den Pushback-Strom wird das zurückgelegte Datum erneut lesen • Man kann also ungestraft ein Byte bzw. Zeichen “vorausschauen“ 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 49 Pushback Strom: Illustration Pushback Datenquelle InputStream Programm read unread pushBack read 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 50 Datenströme • Daten-Ströme ermöglichen das bequeme Lesen und Schreiben von primitiven Datentypen (anstatt einzelner Zeichen) • Ein DataInputStream liest Bytes von einer Eingabe-Quelle und interpretiert spezifische Zeichensequenzen als Darstellungen von Daten unterschiedlicher Typen • Eine Anwendung benutzt DataOutputStream zum Schreiben von Daten, die später von einem DataInputStream gelesen werden können 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 51 Datenströme FilterInputStream FilterOutputStream DataInputStream DataOutputStream readBoolean(): boolean readByte(): byte readShort() : short readChar() : char readInt() : int readFloat() : float writeBoolean(boolean) : void writeByte(byte) : void writeShort(short) : void writeChar(char) : void writeInt(int) : void writeFloat(float) : void 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 52 Datenströme: Beispiel import import import public java.io.FileOutputStream; java.io.DataOutputStream; java.io.IOException class DataStreamExample { … public void writeData(String filename) throws IOException { FileOutputStream fileOut = new FileOutputStream(filename); DataOutputStream out = new DataOutputStream(fileOut); out.writeInt(9); out.writeDouble(Math.PI); out.writeBoolean(true); out.close(); } … 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 53 Datenströme: Beispiel public void readData(String filename) throws IOException { FileInputStream fileIn = new FileInputStream(filename); DataInputStream in = new DataInputStream(fileIn); int i = in.readInt(); double d = in.readDouble(); boolean b = in.readBoolean(); in.close(); System.out.println("Read " + i + ", " + d + ", and " + b + "."); } 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 54 Selbst definierte Prozeßströme • Oft möchten Programmierer eigene Ströme definieren, die Daten filtern und/oder verarbeiten, die von einem Datensenke-Strom gelesen/geschrieben werden – Verarbeitung unabhängig vom Datenformat, z.B. zum Zählen von bestimmten Einheiten im Strom – Verarbeitung abhängig vom Datenformat, z.B. Lesen von Daten organisiert in Spalten und Zeilen • Eigene Filterung oder Verarbeitung kann implementiert werden, indem man von FilterInputStream bzw. FilterOutputStream erbt • Es folgt ein Beispiel eines selbstdefinierten Filterstroms auf Datenströmen, der alle Zeilen im unterliegenden Strom herausfiltert, die einen bestimmten Substring enthalten 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 55 Selbst definierte Prozeßströme import java.io.DataInputStream; import java.io.FilterInputStream; public class GrepInputStream extends FilterInputStream { String substring; DataInputStream in; public GrepInputStream(DataInputStream in, String substring) { super(in); this.in = in; this.substring = substring; } public final String readLine() throws IOException { // Gib die naechste Zeile aus, die den substring enthaelt String line; while ((line = in.readLine()) != null) && line.indexOf(substring) == -1) {} return line; } } 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 56 Selbst definierte Prozeßströme import java.io.*; // IOException, DataInputStream, FileInputStream public class Grep { public static void main(String[] args) { if ((args.length == 0) || (args.length > 2)) { System.out.println("Usage: java Grep <substring> [<filename>]"); System.exit(0); } try { DataInputStream d; if (args.length == 2) d = new DataInputStream(new FileInputStream(args[1])); else Das suchen d = new DataInputStream(System.in); GrepInputStream g = new GrepInputStream(d, args[0]); String line; while ((line = g.readLine()) != null) Hierin suchen System.out.println(line); g.close(); } catch (IOException e) { System.err.println(e); } } } 57 GDI-1.12 - Ein- und Ausgabe in Java 09.01.2003 © MM ... Analyse von Strömen mit StreamTokenizer • java.io.StreamTokenizer unterstützt die Zerlegung von Strömen in Symbole („Tokens“) • Ein Token gibt nur den Typ des gelesenen Elements an • Lesen des nächsten Tokens durch nextToken() • Der Typ wird über das Attribut ttype abgefragt • Im wesentlichen gibt es fünf Arten Tokens: – – – – – StreamTokenizer.TT_NUMBER – Zahl erkannt StreamTokenizer.TT_WORD – Wort erkannt StreamTokenizer.TT_EOL – Zeilenende StreamTokenizer.TT_EOF – Dateiende „Sonstiges“ – ttype codiert das Zeichen, etwa '$', ';' 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 58 Analyse von Strömen mit StreamTokenizer • Abfragen eines gelesenen Werts: 1. Checken des gelesenen Tokens durch Abfrage von ttype (oder direkt bei nextToken()) 2. Falls ttype == StreamTokenizer.TT_WORD: – Zugriff auf String durch Attribut sval 3. Falls ttype == StreamTokenizer.TT_NUMBER: – Zugriff auf Wert durch Attribut nval – Vorsicht, immer double! 4. Ansonsten ist der Wert von ttype das gesuchte Element 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 59 Analyse von Strömen mit StreamTokenizer StreamTokenizer sind anpassbar: • public void wordChars(int low, int hi) – Definieren, was als Wortbestandteil gelten soll – Alle Zeichen im Intervall [low, hi] zählen als Wortbestandteile • public void whitespaceChars(int low, int hi) – Definieren von Leerzeichen – Alle Zeichen im Intervall [low, hi] zählen als „Leerzeichen“ • public void eolIsSignificant(boolean flag) – Definieren, ob Zeilenende relevant ist • public void quoteChar(int quoteChar) – Definieren von Anführungszeichen • Zahlreiche andere Features – bitte in die Doku sehen! 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 60 Analyse von Strömen mit StreamTokenizer Anwendungsbeispiel: • Es soll eine Teeliste für einen Hobbyteetrinker geparst werden • Die Teeliste hat folgende Struktur: – Jeder Tee steht in einer eigenen Zeile • Das Zeilenende ist also relevant! – Die Teenamen sind durch Anführungszeichen „gequotet“ – Hinter dem Teenamen stehen der Reihe nach • Dauer des Ziehenlassens • Anzahl Teelöffel pro Liter Tee • Diese Einträge sind jeweils durch Leerzeichen getrennt Mittels StreamTokenizer ist das Parsen einfach! 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 61 Analyse von Strömen mit StreamTokenizer • Beispieldatei: "Ali Babas 40 Düfte" 180 5 "Asatsuyu" 90 7 "Generic Black Tea" 180 5 "Caramel" 120 6 "Ceylon Pekoe" 120 6 "China Jasmin" 120 6 "Chinesischer Liebestraum" 150 5 "Cool Down" 1273 0 "Einer für alle" 540 6 "Erdbeer-Sahne" 120 6 "Erfrischungskräutertee" 480 6 "Früchtepfund" 420 6 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 62 Analyse von Strömen mit StreamTokenizer public void parseTeas(InputStream is) { // InputStream sei schon geoeffnet... StreamTokenizer stok = new StreamTokenizer(is); stok.eolIsSignificant(true); // EOL melden stok.quoteChar(‘\"‘); // " als Worttrenner int token = 0; String teeName = null; int nrSpoons, nrSecs, coolDown; int eofCode = StreamTokenizer.TT_EOF; // ... weiter auf naechster Folie 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 63 Analyse von Strömen mit StreamTokenizer while ((token = stok.nextToken()) != eofCode) { teaName = stok.sval; // 1. Token token = stok.nextToken(); nrSpoons = (int)stok.nval; // 2. Token token = stok.nextToken(); nrSecs = (int)stok.nval; // 3. Token token = stok.nextToken(); // must be EOL System.out.println(teaName +“:“ +nrSpoons +“ Loeffel, Ziehzeit: “ +nrSecs); } } 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 64 Wahlfreier Zugriff • Bisher wurden nur sequentielle Ströme behandelt, deren Inhalt nur in einer sequentiellen Weise gelesen bzw. geschrieben werden kann • Sequentielle Dateien passen gut zu sequentiellen Speichermedien, z.B. Magnetbänder • Random Access Files erlauben nicht-sequentielle (wahlfreie) Zugriffe auf den Inhalt einer Datei 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 65 Warum wahlfreier Zugriff? • Gegeben ein Dateiarchiv in .zip Format: – Zip-Archive bestehen aus Dateien und sind meist komprimiert, um Speicher zu sparen – Außer Dateien haben zip-Archive ganz zum Schluss zusätzlich einen sog. Dir-Eintrag (von Directory, engl. Verzeichnis), in dem gespeichert wird, wo die einzelnen Dateien innerhalb des Archivs anfangen • Aufgabe: eine bestimmte Datei aus dem Archiv extrahieren 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 66 Darum wahlfreier Zugriff! • Verfahren für einen sequentiellen Strom: – Suche durch das ganze Archiv bis die gewünschte Datei lokalisiert ist – Extrahiere die Datei • Im Durchschnitt wird die Hälfte der Einträge im Archiv gelesen, bevor die gewünschte Datei gefunden ist • Strom mit wahlfreiem Zugriff: – Springe zum Dir-Eintrag und lese den Eintrag der gesuchten Datei – Springe rückwärts zu der Position der Datei – Extrahiere die Datei • Man muss genau zwei Einträge lesen • Das Verfahren ist also wesentlich effizienter! 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 67 Wahlfreier Zugriff in Java • Realisiert durch die Klasse RandomAccessFile • Kann für Lesen und Schreiben benutzt werden - implementiert Schnittstellen DataInput und DataOutput • Ähnelt FileInputStream und FileOutputStream, indem man ein RandomAccessFile auf eine Datei öffnet und beim Erzeugen eines von beiden als Parameter übergibt: – den Dateinamen oder ein Datei-Objekt (File) • Außerdem muss beim Erzeugen spezifiziert werden, ob die Datei nur zum Lesen oder auch zum Schreiben geöffnet wird • Man muss eine Datei lesen können, um auf sie schreiben zu können 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 68 Wahlfreier Zugriff in Java • Erzeugen eines RandomAccessFile, um die Datei “random.txt“ zu lesen: new RandomAccessFile(“random.txt“, “r“); • Erzeugen eines RandomAccessFile, um die Datei “random.txt“ zu lesen und zu schreiben: new RandomAccessFile(“random.txt“, “rw“); • Nachdem die Datei geöffnet ist, kann mit den read und write-Operationen gelesen bzw. geschrieben werden 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 69 Wahlfreier Zugriff in Java • RandomAccessFile unterstützt einen Datei-Zeiger • Ein Datei-Zeiger indiziert die aktuelle Dateiposition • Beim Erzeugen ist der Datei-Zeiger 0 - der Anfang der Datei • Aufrufe von read und writeOperationen verschieben den Datei-Zeiger automatisch um die Anzahl der gelesenen bzw. geschriebenen Bytes 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 70 Wahlfreier Zugriff in Java Außer den üblichen I/O-Operationen, die den Zeiger automatisch verschieben, unterstützt RandomAccessFile drei Operationen zum expliziten Manipulieren des Zeigers: • public int skipBytes(int n) throws IOException • verschiebt den Zeiger vorwärts um n Bytes • public native void seek(long pos) throws IOException • positioniert den Zeiger genau vor das spezifizierte Byte an der Stelle pos • public native long getFilePointer() throws IOException • gibt die aktuelle Position in der Datei zurück 09.01.2003 © MM ... GDI-1.12 - Ein- und Ausgabe in Java 71