Grundzüge der Informatik Teil 5.1 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 15. 7. 2003 © MM ... GDI - 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 durch Programmänderung und Neuübersetzung – Ungeeignet für größere oder komplexere Daten – Keine Interaktionsmöglichkeit – Ergebnisse sind nicht speicherbar 15. 7. 2003 © MM ... GDI - 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“) 15. 7. 2003 © MM ... GDI - 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 15. 7. 2003 © MM ... GDI - 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 15. 7. 2003 © MM ... GDI - 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 („Prozessströme“) 15. 7. 2003 © MM ... GDI - 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 unglücklicher Begriff! – Datensenkeströme / Prozessströme besser: Datenbehälter Klassenhierarchie Klassenhierarchie der derZeichenströme Zeichenströme Klassenhierarchie Klassenhierarchie der derByteströme Byteströme (engl.: Wrapper) Klassen Klassenfür für Datensenkeströme Datensenkeströme Klassen Klassenfür für Prozessströme Prozessströme 15. 7. 2003 © MM ... GDI - 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 • Prozessströme – Daten werden von anderen Strömen gelesen bzw. auf andere Ströme geschrieben – Daten werden nach dem Lesen, bzw. vor dem Schreiben bearbeitet 15. 7. 2003 © MM ... GDI - 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 15. 7. 2003 © MM ... GDI - 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) 15. 7. 2003 © MM ... GDI - 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) 15. 7. 2003 © MM ... GDI - 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 schließt notfalls einen Strom, auf den nicht mehr verwiesen wird – Dennoch sollte das close() immer explizit erfolgen! 15. 7. 2003 © MM ... GDI - 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 14 Hierarchie der Zeichenströme Reader StringReader InputStreamReader InputStreamReader FileReader FilterReader FilterReader PushbackReader PushbackReader BufferedReader BufferedReader LineNumberReader LineNumberReader Die Klassen in schattierten Kästchen sind Prozessströme 15. 7. 2003 © MM ... GDI - 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 Prozessströme 15. 7. 2003 © MM ... GDI - 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 • Im folgenden werden durchgehend nur Zeichenströme betrachtet – Zumeist gibt es entsprechende Klassen für Byteströme – Bitte sehen Sie auch in die Java Dokumentation für Details 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 17 Nutzung der Klasse java.io.Reader • Objekte der Unterklassen von Reader lesen Zeichen aus einer bestimmten Quelle (Datei, Netzwerk,...) • Die wesentlichen Methoden der Klasse sind …: – public abstract int read() throws IOException • Liefert das nächste Unicode-Zeichen im Strom • -1 falls keine weiteren Zeichen vorhanden sind (End of File, „EOF“) • IOException bei allen anderen Problemen – Z.B. Strom bereits geschlossen, Netzwerkverbindung verloren, ... 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 18 Nutzung der Klasse java.io.Reader • public abstract int read() Programm Datenquelle Reader Datenquelle Programm Reader Datenquelle -1 Programm Reader 15. 7. 2003 © MM ... IOException GDI - Ein- und Ausgabe in Java Legende Gelesene Zeichen Nächstes Zeichen EOF geöffneter Strom geschlossener Strom 19 Nutzung der Klasse java.io.Reader • public int read(char[] c) throws IOException, NullPointerException – liest eine Anzahl von Zeichen und speichert sie in dem charArray c. Ruft read() wiederholt auf – gibt Anzahl der gelesenen Zeichen zurück bzw. -1, wenn aufgrund Dateiende (EOF) kein Zeichen gelesen werden konnte – NullPointerException, wenn c == null – IOException, wenn das erste Zeichen nicht gelesen werden konnte aus irgendeinem anderen Grund als EOF – I/O Ausnahmen während des Lesens von anderen Zeichen werden abgefangen (behandelt anstelle sie weiterzugeben) und die Anzahl der Zeichen soweit gelesen zurückgegeben 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 20 Nutzung der Klasse java.io.Reader Programm Datenquelle public int read(char[] c) Legende Reader c Datenquelle Programm Reader c Gelesene Zeichen Nächstes Zeichen -1 Programm Datenquelle EOF Reader c geöffneter Strom geschlossener Strom 15. 7. 2003 © MM ... 3 IOException Programm Datenquelle Reader GDI - Ein- und Ausgabe in Java c 2 21 Nutzung der Klasse java.io.InputStream • public long skip(long n) throws IOException – versucht n Zeichen von dem Eingabestrom zu überspringen – Zurückgegeben wird die Anzahl übersprungener Zeichen – die Anzahl der tatsächlich übersprungenen Zeichen kann kleiner als n sein: • EOF wurde erreicht bevor n Zeichen übersprungen wurden • IOException, wenn das erste Zeichen nicht gelesen werden konnte aus irgendeinem anderen Grund als EOF • public void reset() throws IOException – Versucht den Strom zurückzusetzen (wird nicht von allen Strömen unterstützt) • public int close() throws IOException – Schließt den Strom. Aus einem geschlossenen Strom kann nicht mehr gelesen werden. 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 22 Nutzung der Klasse java.io.Writer • Writer implementiert einen Zeichenstrom, der Zeichen erwartet und sie zu einer Senke schickt • public abstract void write(int c) throws IOException – schreibt ein Zeichen (8 low-order Bits von c) auf die Senke • public void write(char[] c) throws IOException, NullPointerException – schreibt alle Zeichen aus c sequentiell auf den Strom. Ruft write(int) wiederholt auf. • void write(String s, int off, int len) throws IOException – Schreibt die gewählten Zeichen aus dem String durch mehrfaches write(int) auf den Strom 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 23 Nutzung der Klasse java.io.Writer • public abstract void flush() throws IOException – Alle vor dem Aufruf von flush() geschriebenen Zeichen, die eventuell innerhalb des Stroms gepuffert sind, werden sofort auf die Senke des Stroms geschrieben – Der Vorgang wird dabei bei geschachtelten Ausgabeströmen auf alle Ströme fortgesetzt. • Für weitere Methoden siehe die Beschreibung in der Java API Dokumentation 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 24 Datensenkeströme: Überblick Senkentyp Zeichenströme Hauptspeicher StringReader StringWriter Datei FileReader FileWriter StringReader und StringWriter ermöglichen das Lesen von bzw. Schreiben auf Strings im Hauptspeicher 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 25 Dateiströme • Dateiströme stellen Eingabe-/Ausgabe-Ströme zur Verfügung, deren Quellen/Senken Dateien im Dateisystem sind: – FileReader für das Lesen sowie – FileWriter für das Schreiben • 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) 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 26 Die Klasse java.io.FileReader • FileReader stellt einen Zeichen-Eingabe-Datenstrom dar, dessen Quelle eine Datei im Dateisystem ist • public FileInputStream(String path) throws FileNotFoundException – Erzeugt einen neuen Dateilesestrom durch Öffnen der benannten Datei – FileNotFoundException, falls die Datei nicht existiert • Die Beschreibung passt auch auf die Varianten mit Parameter java.io.File oder java.io.FileDescriptor 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 27 Die Klasse java.io.FileReader Beispiel: Ausgeben eines Dateiinhalts auf den Bildschirm Achtung: Ausgabe erfolgt als Integer-Werte! import java.io.FileReader; import java.io.IOException; public class PrintFile { public static void main(String[] args) throws IOException { // setzt mindestens einen Parameter voraus! FileReader in = new FileReader(args[0]); int b; while ((b = in.read()) != -1) System.out.print(b); in.close(); } } 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 28 Die Klasse java.io.FileWriter • FileWriter stellt einen Ausgabe-Strom dar, der in eine Datei schreibt • Beispiel: Die Zeichen von 'a' bis 'z' in eine Datei schreiben: public void writeAToZ(String filename) throws IOException { FileOutputStream out = new FileOutputStream(filename); for (char c = 'a'; c <= 'z'; c++) out.write(c); out.close(); } 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 29 Prozess-Ströme Prozess-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 • … Prozess-Datenstrom Originaler Datenstrom Datensenke 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java Datenquelle 30 Prozess-Ströme Prozess Zeichenströme Filterung FilterReader, FilterWriter Pufferung BufferedReader, BufferedWriter Byte / Char konvertieren InputStreamReader, OutputStreamWriter Zeile zählen LineNumberReader Lesen von Datentypen [ DataInputStream ] Zurücklegen PushbackReader Drucken PrintWriter 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 31 java.io.FilterReader/-Writer • 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 • FilterReader und FilterWriter sind Oberklassen aller Filter-Ströme – Unterklassen von Reader / Writer – Ü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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 32 Die Klassen java.io.PrintStream/-Writer • Unterklasse von FilterOutputStream bzw. Writer • Erweitern einen Ausgabestrom um die Ausgabe einer String-Darstellung verschiedener Daten • PrintStream, PrintWriter lösen nie Ausnahmen aus • Stattdessen wird eine interne Markierung gesetzt, die mit der Methode checkError() abgefragt werden kann • Die Funktionalität der beiden Klassen ist identisch 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 33 Die Klassen java.io.PrintStream/-Writer 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, double, ...) • String • sowie Object • Standarddarstellung ist nicht „lesbar“ 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 34 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 Datenausgabe an 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 35 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“ } 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 36 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) 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 37 Pufferströme • Ein Datenstrom mit Pufferung kapselt einen anderen UrsprungsDatenstrom 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 – Sowie bei explizitem Aufruf der Methode flush() • 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 – Bei leerem Puffer wird erneut vom unterliegenden Strom gelesen 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 38 Pufferströme • BufferedReader und BufferedWriter bieten die Standard-Schnittstelle und Implementierung für Zeichenströme mit Pufferung an – Sie sind Unterklassen von Reader bzw. Writer • BufferedInputStream und BufferedOutputStream bieten die Standard-Schnittstelle und Implementierung für Byteströme mit Pufferung an – Sie sind Unterklassen von FilterInputStream bzw. FilterOutputStream 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 39 Pufferströme • BufferedWriter erweitert Zeichen-Ausgabeströme um die Fähigkeit, Daten zu puffern • Zeichen können geschrieben werden, ohne für jedes Zeichen einen Zugriff auf dem unterliegenden Strom starten zu müssen • 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 40 Puffer-Ströme: java.io.BufferedWriter Datensenke BufferedWriter Programm b b c write bbuf bbuf cbuf write write bbuf bbuf cbuf write bbuf bbuf cbuf flush 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 41 Pufferströme • BufferedReader erweitert Zeichen-Eingabeströme mit Pufferung der Daten • Zeichen können von einem BufferedReader gelesen werden, ohne für jedes zu lesendes Zeichen einen Zugriff auf dem unterliegenden Stream starten zu müssen • Bei der ersten Lese-Operation werden so viele Daten von dem unterliegenden Strom gelesen, wie der Puffer fasst • Folgende Lese-Operationen lesen die Daten vom Puffer und nicht vom System 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 42 Pufferströme: Beispiel import java.io.FileReader; import java.io.BufferedReader; 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]); } } 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 43 Pufferströme: Beispiel public void testBuffer(String fname, int bufferSize) { try { FileReader fis = new FileReader(fname); BufferedReader bis = new BufferedReader(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()); } } 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 44 Pufferströme: Beispiel • Ergebnis für eine 650kB-Datei (x=Puffergröße, y=Zeit [ms]) 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 45 BufferedReader Reader BufferedReader Reader() read() BufferedReader(Reader in) BufferedReader(Reader in, int size) readLine() LineNumberReader LineNumberReader(Reader in) LineNumberReader(Reader in, int size) getLineNumber() 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 46 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“ 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 47 Pushback Strom: Illustration Datenquelle Programm PushbackReader read pushBack unread read 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 48 Datenströme • Daten-Ströme ermöglichen das bequeme Lesen und Schreiben von primitiven Datentypen (anstatt einzelner Zeichen) • Sie existieren nur als Byte-, nicht als Zeichenströme • Ein DataInputStream liest Bytes von einer EingabeQuelle 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 49 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 50 Datenströme: Beispiel import java.io.FileOutputStream; import java.io.DataOutputStream; import java.io.IOException public 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(); } // … 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 51 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 + "."); } 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 52 Selbst definierte Prozessströ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 FilterReader bzw. FilterWriter erbt • Es folgt ein Beispiel eines selbstdefinierten Filterstroms auf Datenströmen, der alle Zeilen im unterliegenden Strom herausfiltert, die einen bestimmten Substring enthalten 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 53 Selbst definierte Prozessströme import java.io.DataInputStream; import java.io.FilterInputStream; import java.io.IOException; 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; } } 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 54 Selbst definierte Prozessströ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); } } } 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 55 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 liefert (als int) • 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 '$', ';' 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 56 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 57 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! 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 58 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! 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 59 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 60 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 61 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); } } 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 62 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 63 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 so genannten Dir-Eintrag (von Directory, engl. Verzeichnis) – Hier wird gespeichert wird, wo die einzelnen Dateien innerhalb des Archivs anfangen • Aufgabe: eine bestimmte Datei aus dem Archiv extrahieren 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 64 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! 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 65 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 66 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 67 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 68 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 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java 69 Writer Reader StringWriter StringReader InputStreamReader InputStreamReader FilterReader FilterReader BufferedReader BufferedReader FileReader PushbackReader PushbackReader LineNumberReader LineNumberReader OutputStreamWriter OutputStreamWriter FileWriter FilterWriter FilterWriter BufferedWriter BufferedWriter PrintWriter PrintWriter Zeichen Bytes InputStream FileInputStream FilterInputStream FilterInputStream BufferedInputStream BufferedInputStream DataInputStream DataInputStream LineNumberInputStream LineNumberInputStream OutputStream FileOutputStream FilterOutputStream FilterOutputStream PushbackInputStream PushbackInputStream 15. 7. 2003 © MM ... GDI - Ein- und Ausgabe in Java BufferedOutputStream BufferedOutputStream DataOutputStream DataOutputStream PrintStream PrintStream 70