Vorlesung 11. Sitzung Grundlegende Programmiertechniken Wintersemester 2007/2008 Dozent Nino Simunic M.A. Computerlinguistik, Campus DU Grundlegende Programmiertechniken, WS 2007/2008 Datenströme Externe Informationen lesen/schreiben Häufige Software-Operationen: -3- (Ein-)Lesen von Daten/Informationen aus einer externen Quelle in die Java-Umgebung einlesen Schreiben von Informationen aus dem Programm in ein externes Ziel. In allen Fällen sind E/A Datenströme die Basis. Wichtige Merkmale der Ströme Ein Strom kann die verschiedensten Typen von Quellen und Zielen repräsentieren. Ströme unterstützen verschiedene Typen von Daten -4- Dateien, Geräte, andere Programme, Arrays im Speicher, … Bytes, primitive Datentypen, lokalisierte Zeichen, Objekte, … Einige Strom-Typen befördern Daten ausschließlich, andere dagegen manipulieren sie auf sinnvolle Weise. Grundlage aller E/A Operationen: Lesen In allen Fällen gilt beim Lesen von Daten: -5- Ein Strom ist eine Daten-Sequenz. Ein Programm verwendet Input-Streams, um Daten sequenziell/stückweise zu lesen. Grundlage aller E/A Operationen: Schreiben In allen Fällen gilt beim Schreiben von Daten: -6- Ein Strom ist eine Daten-Sequenz. Ein Programm verwendet Output-Streams, um Daten sequenziell/stückweise zu lesen. Einleitung : E/A Ströme in Java (3) Algorithmus der sequentiellen Datenverarbeitung sowohl beim Lesen als auch beim Schreiben: Lesen Schreiben Strom öffnen Mehr Informationen? Lese Informationen Strom schließen Strom öffnen Mehr Informationen? Schreibe Informationen Strom schließen Mehr Informationen? Informationen? zu zuverstehen verstehenals alsSchleife Schleife Mehr mit Bedingung BedingungSolange SolangeInformationen Informationenvorhanden vorhanden mit -7- Paket java.io java.io enthält Strom-Klassen für die Umsetzung von E/A Aktionen Strom-Klassen sind grundlegend auf zwei Klassenhierarchien verteilt: Zeichen-Ströme: »Textuelle« Informationen Byte-Ströme: Informationen als Rohdaten -8- Rohdaten-/Byte-Ströme Byte-Strom Klassen sind Ableitungen von InputStream und OutputStream. Typische Anwendungen: Lesen und Schreiben binärer Daten: Bilder, Sounds, Objekte, … -9- Byte-Ströme: Einige Subklassen von InputStream und OutputStream abstract Byte-Ströme: Byte-Ströme:Öffnen/Lesen Öffnen/Lesen Byte-Ströme: Byte-Ströme:Öffnen/Schreiben Öffnen/Schreiben InputStream OutputStream … … FilterInputStream BufferedInputStream FileInputStream InflaterInputStream FilterOutputStream BufferedOutputStream … GZIPInputStream ZipInputStream Wahlfreie DV (vs. sequentielle DV) -1010- FileOutputStream Printstream … Details der Byte-I/O Superklassen (1) InputStream … InputStream definiert InputStream definiert u.a. u.a. Methoden Methoden für für das das FileInputStream FilterInputStream Lesen Lesen von von Bytes Bytes und und Byte-Arrays: Byte-Arrays: int read() int read() BufferedInputStream InflaterInputStream int int read(byte read(byte cbuf[]) cbuf[]) … int read(byte cbuf[], int offset, int read(byte cbuf[], int offset, int )) GZIPInputStream ZipInputStream int length length -1111- Details der Byte-I/O Superklassen (2) OutputStream … OutputStream OutputStream definiert definiert ähnliche ähnliche Methoden Methoden FilterOutputStream FileOutputStream zum zum Schreiben Schreiben von von Bytes: Bytes: int write(int c) int write(int c) BufferedOutputStream int int write(byte write(byte cbuf[]) cbuf[]) int cbuf[], Printstream int write(byte write(byte cbuf[], int int … offset, offset, int int length) length) -1212- Öffnen/Schließen von Streams Alle Ströme werden bei ihrer Erzeugung automatisch geöffnet Ströme sollten stets nach erfolgter Verwendung via close()-Methode geschlossen werden. -1313- Gilt für InputStream's, OutputStream's, aber auch die noch folgenden Reader's, und Writer's Der garbage collector schließt Ströme implizit, wenn ein Strom-Objekt nicht mehr referenziert wird. Verwendung von Byte-Strömen Demonstration von Byte-Strömen anhand von E/A Byte-Strömen fürs Lesen/Schreiben von Dateien. Beispiel-Projekt: -1414- Andere Typen von Byte-Strömen werden fast analog hierzu verwendet: Hauptunterschied ist die Art Ihrer Erzeugung. Kopiere Informationen aus einer Datei in eine andere Verwendete Strom-Klassen im Projekt: FileInputStream, FileOutputStream CopyBytes.java import java.io.*; public class CopyBytes { FileIS,FileOS FileOS Konstruktoren: Konstruktoren: FileIS, -Dateiname als alsZeichenkette Zeichenkette -Dateiname -Ein File-Objekt File-Objekt(Hausaufgabe) (Hausaufgabe) -Ein -Ein FileDescriptor FileDescriptorObjekt Objekt -Ein public static void main(String[] args) throws IOException { FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream("test.txt"); out = new FileOutputStream("test_copy.txt"); int c; while ((c = in.read()) != -1) { out.write(c); } } finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } } } -1515- Ströme und Ausnahmen In vielen Fällen können Ströme Ausnahmen auslösen -1616- Datei nicht gefunden/korrupt/schreibgeschützt, Netzwerkverbindung down, … In ebenso vielen Fällen verpflichten die I/O-Klassen den Programmierer zur Ausnahmebehandlung Die Ausnahmen sind ebenfalls in java.io zu finden. Welche Ausnahme erzeugt werden kann, steht in der Java-API, bzw. sagt Ihnen die IDE bzw. der Compiler Wann man Byte Ströme nicht verwenden sollten CopyBytes funktioniert zwar (Text), stellt jedoch low-level E/A dar, welches i.d.R. vermieden werden sollte. Nebenbei: Byte Ströme sind auf ISO-Latin-1 8-bit Bytes begrenzt. Kann zu Problemen führen. Idealfall ist die Nutzung entsprechender Ströme für komplexe Daten. Hier also Zeichen-Ströme! Noch einmal: Vermeiden Sie low-level E/A. -1717- test.txt enthält Zeichen-Daten, also komplexe Daten Warum dann überhaupt ansehen? Alle anderen StromTypen haben Byte-Ströme als Grundlage! ;-) Zeichen-Ströme (engl. character streams) Im Allgemeinen ist E/A mit Zeichenströmen nicht sonderlich komplizierter als mit Byte-Strömen. Wichtiges Konzepte hier: Zeichenkodierungen. -1818- Java Plattform speichert Zeichen auf Unicode Basis Internes Format wird automatisch übersetzt in/von der lokalen Zeichencodierung Man kann den Automatismus nutzen, oder aber auch bequem eigene Zeichenkodierungen beim Erzeugen der Ströme einfach angeben, falls erforderlich! Zeichen-Ströme Reader und Writer sind abstrakte Superklassen aller Zeichen-Ströme Ihre Subklassen implementieren spezifische Ströme für Datei E/A (analog zu den Byte-Strömen) -1919- Ströme, die direkt in data sinks (Datensenken) lesen/schreiben (analog zum letzten Beispiel) Filter-Ströme, die nicht direkt mit der Datensenke verbunden sind und die Daten sinnvoll vor dem Einlesen/Schreiben prozessiert Zeichen-Ströme: Einige Subklassen von Reader und Writer abstract Zeichen-Ströme:Öffnen/ Zeichen-Ströme:Öffnen/Lesen Lesen Zeichen-Ströme: Zeichen-Ströme:Öffnen/Schreiben Öffnen/Schreiben Reader Writer … BufferedReader InputStreamReader … … LineNumberReader FilterWriter BufferedWriter FileReader OutputStreamWriter … FileWriter -2020- Details der Zeichen-I/O Superklassen (1) Reader und InputStream definieren ähnliche APIs, jedoch für unterschiedliche Datentypen: -2121- int read() int read(char cbuf[]) int read(char cbuf[], int offset, int length) Analog zu InputStream: Methoden zum Flush'en, Überspringen, Markieren, Zurücksetzen http://java.sun.com/javase/6/docs/api/java/io/Reader.html abstract void close() void mark(int readAheadLimit) -2222- Resets the stream. long skip(long n) Tells whether this stream is ready to be read. void reset() Tells whether this stream supports the mark() operation. boolean ready() Marks the present position in the stream. boolean markSupported() Closes the stream and releases any system resources associated with it. … Skips characters. Details der Zeichen-I/O Superklassen (2) Writer und OutputStream ähneln sich ebenfalls in den definierten Methoden -2323- write(char[] cbuf) Writes an array of characters. abstract void write(char[] cbuf, int off, int len) Writes a portion of an array of characters. void write(int c) Writes a single character. void write(String str) Writes a string. void write(String str, int off, int len) Writes a portion of a string. Wie gehabt: Öffnen der Ströme automatisch bei Erzeugung des jeweiligen Strom-Objekts Anwendungsdemonstration am Beispiel Datei-E/A Analog zu Byte-Strömen: Klassen vorhanden, welche Datei-E/A auf Zeichenstrom-Ebene realisieren Beispiel-projekt: CopyCharacters -2424- Gleiche Funktionalität wie CopyBytes. Wichtigster Unterschied: Anstelle von FileInputStream und FileOutputStream für E/A: FileReader, FileWriter CopyCharacters.java import java.io.*; public class CopyCharacters { public static void main(String[] args) throws IOException { FileReader inputStream = null; FileWriter outputStream = null; try { inputStream = new FileReader("test.txt"); outputStream = new FileWriter("test_copy2.txt"); int c; while ((c = inputStream.read()) != -1) { outputStream.write(c); } } finally { if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.close(); } } } -2525} Zeichen-Ströme, die Byte-Ströme verwenden Zeichenströme sind oft "Wrapper" für Byte-Ströme Der Zeichenstrom verwendet den Bytestrom für physikalisches I/O Der Zeichenstrom übernimmt die Aufgabe der Übersetzung zwischen Zeichen und Bytes FileReader z.B. verwendet FileInputStream FileWriter verwendet FileOutputStream Insgesamt zwei »general-purpose« Byte-zuZeichen »Brücken«-Ströme -2626- InputStream / Reader OutputStream / Write Zeilen-basiertes I/O Zeichen E/A wird i.d.R. in größeren Einheiten (als nur einzelne Zeichen) realisiert. Eine typische Einheit: Zeile (eines Texts) Beispiel: CopyLines -2727- Eine Zeichenkette, welcher mit einem Zeilenumbruch endet. Verwendet BufferedReader.readLine und PrintWriter.println für zeilenweises E/A CopyLines.java import java.io.*; public class CopyLines { public static void main(String[] args) throws IOException { BufferedReader inputStream = null; PrintWriter outputStream = null; try { inputStream = new BufferedReader(new FileReader("test.txt")); outputStream = new PrintWriter(new FileWriter("text_out.txt")); String l; while ((l = inputStream.readLine()) != null) { outputStream.println(l); } } finally { if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.close(); } } } -2828} Filterströme: Puffer-Ströme (engl. buffered streams) Buffered I/O (gepuffertes E/A) Um diesen Overhead zu vermeiden: Buffered I/O streams -2929- Means that each read or write request is handled directly by the underlying OS. Can make a program much less efficient, since each such request often triggers disk access, network activity, or some other operation that is relatively expensive. Buffered input streams read data from a memory area known as a buffer; the native input API is called only when the buffer is empty. Similarly, buffered output streams write data to a buffer, and the native output API is called only when the buffer is full. Buffered Streams (2) Ein Programm kann einen ungepufferten Strom in einen gepufferten Strom konvertieren Ungepufferter Strom wird vom Puffer-Strom »verpackt«, bzw. diesem als Objekt übergeben Beispiel: CopyCharacters mit gepuffertem Strom Einzige Änderung: Dem ungepuffertem wird ein gepufferter Strom zwischengeschaltet. Buffered Stream BufferedReader BufferedReader new BufferedReader(…) -3030- Unbuffered Stream FileReader FileReader new Filereader(…) CopyCharacters2.java import java.io.*; public class CopyCharacters2 { public static void main(String[] args) throws IOException { BufferedReader inputStream = null; BufferedWriter outputStream FileReader inputStream = null; FileWriter outputStream = null;= null; try { inputStream = new FileReader("test.txt"); inputStream = new BufferedReader(new FileReader("test.txt")); BufferedWriter(new FileWriter("test_copy.txt")); outputStream = new outputStream = new FileWriter("test_copy2.txt"); int c; while ((c = inputStream.read()) != -1) { VieroutputStream.write(c); gepufferte Stromklassen Stromklassen zum zum Vier gepufferte } Wrappen ungepufferter Ströme: Ströme: Wrappen ungepufferter } finally { if (inputStream != null)BufferedOutputStream { BufferedInputStream, erstellten BufferedInputStream, BufferedOutputStream erstellten inputStream.close(); gepufferte Byte-Ströme gepufferte Byte-Ströme } if (outputStream != null) { BufferedReader und BufferedReader undBufferedWriter BufferedWritererzeugen erzeugengepufferte gepufferte outputStream.close(); Zeichenströme } Zeichenströme } } -3131} Flush'en von gepufferten Strömen It often makes sense to write out a buffer at critical points, without waiting for it to fill. Some buffered output classes support autoflush, specified by an optional constructor argument. When autoflush is enabled, certain key events cause the buffer to be flushed. -3232- This is known as flushing the buffer. For example, an autoflush PrintWriter object flushes the buffer on every invocation of println or format. To flush a stream manually, invoke its flush method. The flush method is valid on any output stream, but has no effect unless the stream is buffered. E/A auf der Kommandozeile -3333- Programm verwenden häufig die Kommandozeile, um mit dem Benutzer zu interagieren Eine Möglichkeit der Umsetzung: Verwenden von Standard-Strömen Standard Streams (1) Standard Streams sind Features von vielen Betriebssystem. Standardmäßig lesen Sie Input der Tastatur und schreiben auf den Bildschirm Die Java Plattform unterstützt drei Standard-Ströme: Standard Input: Standard Output: Standard Error: Zugriff via System.in Zugriff via System.out Zugriff via System.err Standard Output, Standard Error sind Ausgabe-Ströme. Ihre jeweilige Formatierung erlaubt jedoch, Fehler von regulären Ausgaben schnell zu unterscheiden. -3434- Objekte dieser Ströme werden automatisch definiert und müssen nicht geöffnet werden. Standard Streams (2): Vorsicht Aus historischen Gründen: Die Standardströme sind keine Zeichen-Ströme, sondern Byte-Ströme System.out und System.err werden als PrintStream Objekte definiert. Obwohl sie Byte-Ströme sind, verwendet PrintStream einen eigenen internen Zeichen-Strom, um viele der Features von Zeichen-Ströme zu emulieren. Im Gegensatz dazu ist System.in ein Byte-Strom with ohne Zeichenstrom-Funktionalität. Um den Standard Input als Zeichenstrom zu nutzen, muss System.in gewrappt werden: System.in in InputStreamReader: -3535- InputStreamReader stdin = new InputStreamReader(System.in); Hausaufgabe Kapitel»Scanning and Formatting« lesen, Quelltexte implementieren, ausprobieren, erweitern. -3636- Download ab morgen auf unserer Seite Evtl. Zusatzliteratur aus Head First Java Übungsaufgaben nächste Woche setzen das Lesen des Kapitels voraus. Fragen? Aufschreiben! Wie immer: Auch Folien nachbereiten, Quelltexte ausprobieren (auch die der Hausaufgaben-Kapitel), erweitern, etc. Next: Collections, Generezität, Ereignisbasierte Programmierung, GUI-Entwicklung Warnung: Evtl. müssen wir 2h in den Ferien machen Referenzen Sharon Zakhour, Scott Hommel, Jacob Royal, Isaac Rabinovitch, Tom Risser, Mark Hoeber. 2006.The Java™ Tutorial Fourth Edition: A Short Course on the Basics. Addison Wesley Professional. Java (1.6) API Dokumentation -3737- http://java.sun.com/javase/6/docs/api/