Kapitel 1 Dateien und Streams 1.1 Dateien und Dateiverzeichnisse Die Klassen File und URL Die Daten, welche von Programmen bearbeitet werden, sowie die Programme selbst werden in Dateien abgespeichert. Dateien können angelegt werden und der Übersicht halber in Dateiverzeichnisse strukturiert werden und sie können umbenannt und auch wieder gelöscht werden. Die Verwaltung von Dateien und Dateiverzeichnissen wird in Java von der Klasse File übernommen. Sie liefert eine abstrakte, systemunabhängige Repräsentation für derartige Objekte. Der Zugriff auf die in einer Datei gespeicherten Daten erfolgt über Datenströme, die in der Java-Literatur auch als Streams bezeichnet werden und in den folgenden Unterkapiteln beschrieben werden. Ein File-Objekt kann über den mehrfach überladenen Konstruktor der Klasse File instantiiert werden. In den Konstruktordefinitionen werden Referenzen vom Typ der Klassen String, File und URI übergeben. Die String- und File-Referenzen verweisen auf konkrete bzw. abstrakte Verzeichnis- oder Dateinamen. Über URI (»uniform resource identifier«)-Referenzen können Ressourcen im Netzwerk eindeutig identifiziert werden. URIs können vom Typ URL (»uniform resource locator«) oder URN (»uniform resource name«) sein. URL-Referenzen können auf Ressourcen im World Wide Web (WWW) wie Dateien und Dateiverzeichnisse, aber auch auf kompliziertere Objekte wie Abfragen auf Datenbanken oder Suchmaschinen zeigen. URLs beinhalten die Location (Ort) eines Dokuments und die wichtigsten unterstützten Zugriffsarten. URNs spezifizieren die Art von Ressourcen, um diese zu identifizieren, ohne sie gleichzeitig zu referenzieren. Beispiele dafür sind die Protokolle http (HypertextTransfer Protocol), ftp (File Transfer Protocol), file (Dateien im lokalen Dateisystem), mailto (E-Mail-Adressen) und news (NewsGroup oder Newsartikel). Ein Beispiel für eine URL ist http://java.sun.com/index.jsp, die das Dokument index.jsp auf einem Host von Sun referenziert. Ein weiteres Beispiel ist file:C:/Programme/Java/jdk1.6/docs/api/index.html, welche die Datei index.html im angegebenen Directory auf dem lokalen Rechner referenziert. http ist ein Protokoll, das im Allgemeinen keine Authentifizierung des Benutzers verlangt. Bei anderen wie z.B. ftp ist meistens eine Benutzer-/Passwort-Identifizierung notwendig, damit eine Datei übertragen werden kann. Diese Beispiele zei- 27 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams gen, dass eine URL über einen String dargestellt werden kann, der eine Ressource im Internet eindeutig identifiziert. Die Methode toURL() der File-Klasse wurde mit Java 6 als »deprecated« gekennzeichnet; weil diese falsche URLs liefert, falls Datei- oder Dateiverzeichnisnamen Leerzeichen beinhalten. Der einfachste Weg, eine korrekte URL-Instanz für eine File-Instanz zu erzeugen ist der gekettete Methodenaufruf toURI().toURL() an einem File-Objekt. Aufgabe 1.1 Die Konstruktoren und Methoden der Klasse File Definieren Sie eine Klasse DateiundVerzeichnisVerwaltung, in der Sie FileObjekte über die unterschiedlichen Konstruktoren erzeugen. Im Konstruktor der Klasse File kann ein Verzeichnisname als String-Referenz übergeben werden, der in einen abstrakten Pfadnamen konvertiert wird. Anschließend muss die Methode mkdirs() aufgerufen weden, die das entsprechende Unterverzeichnis erzeugt. Es können aber auch der Verzeichnisname gefolgt vom Dateinamen bzw. Verzeichnis- und Dateinamen als String-Referenzen übergeben werden. Anschließend muss die Methode createNewFile() aufgerufen werden, welche die Datei erzeugt. Lassen Sie sich die Syntax von URL- und URI-Schemata mit Hilfe der Methoden toURL() und toURI() der Klasse File anzeigen, um herauszufinden, wie das im Konstruktoraufruf übergebene Argument in diesem Fall auszusehen hat. So liefert z.B. new File("C:/EJ_Uebungsbuch2/").toURI().toURL(); die URL file:/ C:/EJ_Uebungsbuch2/ und genau diese Syntax muss im Konstruktoraufruf verwendet werden. Erzeugen Sie ein weiteres File-Objekt, indem Sie im Konstruktoraufruf einen als Argument im Programmaufruf angegebenen Datei- oder Verzeichnisnamen übergeben. Definieren Sie der Einfachheit halber ein File-Array, um darin alle erzeugten Verzeichnisse und Dateien zu hinterlegen. Die Klasse File stellt Methoden zur Verfügung, mit denen Informationen über eine Datei oder ein Dateiverzeichnis geholt werden können. Rufen Sie die Methoden exists(), isFile() und isDirectory() an allen von Ihnen erzeugten File-Objekten auf, um zu sehen, ob die Konstruktoraufrufe zu den gewünschten Ergebnissen geführt haben. Zeigen Sie die Namen aller Verzeichnisse und Dateien am Bildschirm an, indem Sie die Methode getName() der Klasse File an den von Ihnen erzeugten Instanzen der Klasse aufrufen. Testen Sie auch andere Methoden der Klasse File, wie z. B. renameTo() und delete(), mithilfe derer Sie Dateien umbennen bzw. löschen können. 28 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.1 Dateien und Dateiverzeichnisse Hinweise für die Programmierung: Auf manchen Systemen liefert die Methode delete() den Rückgabewert true, obwohl die Datei nicht gelöscht wurde, und beim Aufruf der Methode getName() nach renameTo() wird noch immer der alte Dateiname angezeigt, obwohl die Datei umbenannt wurde. Java-Dateien: DateiundVerzeichnisVerwaltung.java Programmaufruf: java DateiundVerzeichnisVerwaltung C:/EJ_Uebungsbuch2/, java DateiundVerzeichnisVerwaltung DateiundVerzeichnisVerwaltung.java Aufgabe 1.2 Auflisten von Einträgen aus Dateiverzeichnissen Über den Methodenaufruf getProperty("user.home") der Klasse System kann das Home-Verzeichnis des aktuellen Benutzers ermittelt werden, wobei user.home eine Systemeigenschaft (»system property«) bezeichnet. Erstellen Sie eine Klasse DateiundVerzeichnisListen, in der Sie mithilfe der Methode listFiles() der Klasse File sowohl die Unterverzeichnisse und Dateien des Home-Verzeichnisses auflisten als auch die eines von Ihnen definierten Verzeichnisses. Dieses soll ein weiteres Unterverzeichnis enthalten und darin sollen mehrere Dateien hinterlegt werden. Definieren Sie eine Klassenmethode anzeige(), um die Namen von Verzeichnissen und Dateien aus den als Rückgabewert der Methode listFiles() erzeugten File-Arrays am Bildschirm auszugeben. Ermitteln Sie alle verfügbaren Laufwerke auf Ihrem Rechner mithilfe der Methode listRoots() der Klasse File und deren Speicherkapazität wie auch den darauf noch vorhandenen Platz mithilfe der Methoden getTotalSpace() und getFreeSpace() der gleichen Klasse. Diese Methoden wurden mit der Version 6.0 von Java eingeführt. Java-Dateien: DateiundVerzeichnisListen.java Programmaufruf: java DateiundVerzeichnisListen Aufgabe 1.3 Die Klassen FileFilter und FilenameFilter Über den Methodenaufruf getProperty("user.dir") der Klasse System kann das aktuelle Verzeichnis des Benutzers ermittelt werden. 29 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Erstellen Sie eine Klasse DateiundVerzeichnisFilter, in der Sie das aktuelle Verzeichnis ermitteln und dieses nach vorgegebenen Filterdefinitionen mithilfe der Methoden list(FilenameFilter filter) und listFiles(FileFilter filter) der Klasse File durchsuchen. Definieren Sie dazu mittels anonymer Klassen drei benutzerdefinierte Filter, die das Interface FilnameFilter implementieren und aus dem aktuellen Verzeichnis jeweils alle Dateien mit den Endungen .java und .class filtern. Ein weiterer benutzerdefinierter Filter vom Typ der Klasse FileFilter wird mittels einer weiteren anonymen Klasse implementiert und soll dazu dienen, bestimmte Dateien oder Unterverzeichnisse über ihren Pfadnamen zu ermitteln. In den anonymen Klassen sollen die Methoden accept() des Interfaces FilenameFilter und der Klasse FileFilter implementiert bzw. überschrieben werden. Weil die Methoden list() und listFiles() Arrays von unterschiedlichen Datentypen (String bzw. File) liefern, soll eine generische Methode anzeigeArray() definiert werden, der beide Arten von Arrays für die Anzeige der Datei- und Verzeichnisnamen als Argumente im Methodenaufruf übergeben werden können. Java-Dateien: DateiundVerzeichnisFilter.java Programmaufruf: java DateiundVerzeichnisFilter 1.2 Die Definition und Klassifikation von Streams Streams (Datenströme) sind in Java Objekte, die uns erlauben, in einem Programm Daten von einer Informationsquelle einzulesen und zu einem Ziel zu senden. Ein Stream kann von einem peripheren Gerät, einer Datei, aus dem Speicher des Rechners, von einem Thread, einem Netzwerk-Socket sowie von einem anderen Stream kommen. Ein solcher wird auch als Input-Stream bezeichnet. Die Ziele eines Streams können die gleichen wie seine Quellen sein, denen wäre nur noch der Drucker hinzuzufügen. In diesem Fall wird von Output-Streams gesprochen. Ein Stream muss geöffnet werden, seine Daten (Bytes oder Zeichen) gelesen oder geschrieben werden und der Stream wieder geschlossen werden. Alle Stream-Klassen von Java befinden sich im Paket java.io. Die einzelnen Klassen unterscheiden sich dadurch, dass die einen Byte- und die anderen Characterorientiert (was auch als zeichenorientiert bezeichnet wird) arbeiten. Nützliche Bemerkungen zu Datenkonvertierungen für Ein-/Ausgaben An dieser Stelle sei noch mal darauf hingewiesen, dass ein Bit (»binary digit«) die kleinste Dateneinheit in einem Computer ist und die Werte 0 und 1 aufnehmen kann. Ein Byte besteht aus 8 Bits und ein Java-Unicode-Zeichen besteht aus zwei Bytes. 30 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.2 Die Definition und Klassifikation von Streams Unicode ist ein international standardisierter Zeichensatz, der als Untermenge den ASCII-Zeichensatz enthält. Mit dem Unicode-Zeichensatz wird einem Character eine 16 Bit breite ganze Zahl (die in der Java-Literatur auch als Unicode-Code-Point bezeichnet wird) zugeordnet. Der Unicode-Code-Point eines Zeichens kann mithilfe der mit der Version 5.0 von Java implementierten Methode codePointAT() der Klasse String ermittelt werden. Dadurch, dass im Unicode-Zeichensatz jedes Zeichen mit einer Breite von 16 Bits dargestellt wird, kann eine sehr große Anzahl von Zeichen erfasst und damit der Zeichensatz vieler Sprachen dieser Welt abgedeckt werden. Im Gegensatz dazu ordnet der altbekannte ASCII-Zeichensatz 128 ganze Zahlen mit einer Breite von 7 Bits den Englisch-orientierten Characters zu, wie zum Beispiel den Großbuchstaben des Alphabetes von A bis Z die Zahlen 65 bis 91 und den Kleinbuchstaben von a bis z die Zahlen 97 bis 123. Daher kommt auch die Bezeichnung 7-Bit ASCII-Key-Code, die oft verwendet wird. Zum Verständnis der weiteren Darbietungen ist wichtig zu wissen, dass die Zeichen des ASCII-Zeichensatzes im Unicode-Zeichensatz mit dem gleichen Code-Wert (der gleichen ganzen Zahl) dargestellt werden. Ähnliches gilt für den 8-Bit-Code »ISO-8859-1« für die westeuropäischen Sprachen, bei dem die Codes von 0 bis 127 die 128 ASCII-Zeichen enthalten und die Codes 128 bis 255 weitere Sonderzeichen wie z.B. deutsche Umlaute und französische Akzente. Es gibt aber auch andere 8-Bit-Codes, z.B. für die osteuropäischen Sprachen, Griechisch, Kyrillisch u.a. Die meisten Rechnersysteme unterstützen nicht den gesamten Unicode-Zeichensatz, sondern nur Teile davon, wie z.B. ISO-8859-1. Java-Programme werden meistens mit einem ASCII- oder ISO-8859-1-basierten Editor erfasst. Um die Anzahl der Zeichen, die in einem solchen Editor eingegeben werden können, zu vergrößern, definiert Java die Unicode-Escape-Sequenz, welche eine Folge von ASCII-Zeichen in Form von \uxxxx für die Repräsentation eines Unicode-Zeichens nutzt. In dieser Darstellung steht jedes x für eine hexadezimale Zahl und es können damit alle Unicode-Zeichen repräsentiert werden (so ist z.B. für die Großbuchstaben von A bis Z der Wertebereich von \u0041 bis \u005a reserviert, für die Kleinbuchstaben von a bis z der Wertebereich von \u0061 bis \u007a und für die Zahlen von 1 bis 9 der Wertebereich von \u0030 bis \u0039). Eigentlich gibt es eine 1:1-Abbildung zwischen Unicode-Zeichen und den Zeichen dieser Zeichensätze. So entsprechen die Unicode-Zeichen \u0020 bis \u007E den ASCII- und ISO8859-1-Zeichen 0x20 bis 0x7E und die Unicode-Zeichen von \u00A0 bis \u00FF sind mit den ISO8859-1-Zeichen 0xA0 und 0xFF identisch. Bevor es mit dem Compilieren losgeht, prüft der Java-Compiler ob der Programmcode in Unicode-Zeichencodes vorliegt. Werden 7-Bit ASCII- oder 8-Bit ISO-88591-Zeichencodes vorgefunden, werden diese durch das Hinzufügen von Bits gleich 0 in Unicode-Zeichencodes umgesetzt. Eine Ausnahme bilden nur die Unicode- 31 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Escape-Sequenzen. Für diese werden die 6 ASCII-Zeichen, welche die Sequenz repräsentieren, in einen 16 Bit Unicode-Zeichencode umgesetzt. Aufgabe 1.4 Operationen mit primitiven Datentypen Wir wollen eine Testklasse TestLossofPrecision für Operationen mit primitiven Datentypen erstellen, um deren Ergebnisse in den weiteren Aufgaben beim Konvertieren von Daten zu nutzen. Die Klasse soll eine Methode mit der Signatur public static Number[] add(Number zahl1, Number zahl2) definieren, die zwei beliebige Instanzen von Wrapper-Klassen übergeben bekommt, mithilfe der Methoden intValue(), floatValue(), byteValue() etc. deren primitive Werte ermittelt und diese in unterschiedlichen Kombinationen addiert. Alle errechneten Ergebnisse werden in einem Number-Array für die Rückgabe hinterlegt. In der main()-Methode der Klasse sollen lokale Variablen von allen primitiven Datentypen definiert, initialisiert und addiert werden, um herauszufinden, welche Operationen möglich sind und welche Art von Castings dafür erforderlich sind. Gleichzeitig soll die Methode add() mit Referenzen auf Objekte von Unterklassen des Parametertyps mehrmals aufgerufen werden. In der Klasse TestLossPrecision wird auch ein byte-Array mit 26 Elementen definiert, denen der 7-Bit ASCII-Key-Code (gleich dem Unicode-Code-Point) für alle Großbuchstaben des Alphabetes zugewiesen wird (d.h. die Werte 65 bis 91). Diese sollen mithilfe der Methode Character.toLowerCase() in Kleinbuchstaben und mit der Methode byteValue() der Wrapper-Klasse Byte sowie der Addition des int-Wertes 32 auf die ursprünglichen Werte in deren 7-Bit ASCII-KeyCode (d.h. die Werte von 97 bis 123) umgesetzt werden. Zeigen Sie alle erzielten Ergebnisse am Bildschirm an. Java-Dateien: TestLossPrecision.java Programmaufruf: java TestLossPrecision Byteorientierte Streams Byte-Streams können als Instanzen der Klassen InputStream und OutputStream gebildet werden. Die als Instanzen dieser Klassen erstellten Streams benutzen als kleinste Ein- und Ausgabeeinheit 1 Byte. InputStream ist die Oberklasse aller byteorientierten Klassen zum Einlesen von Daten und OutputStream die Oberklasse aller byteorientierten Klassen zum Schreiben von Daten. 32 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.2 Die Definition und Klassifikation von Streams Diese Klassen werden in der Regel für den Zugriff auf binäre Daten verwendet, da ein Zugriff auf Textdaten sich damit nicht allzu einfach gestalten lässt und Probleme beim Konvetieren entstehen können. Zeichenorientierte Streams Die Klassen Reader und Writer existieren parallel zu den byteorientierten Klassen und dienen der Definition von Character-Streams. Diese ermöglichen eine korrekte Konvertierung zwischen den von Java intern genutzten Unicode-Zeichen und den jeweiligen Betriebssystem-Zeichensätzen. Reader ist die Oberklasse aller zeichenorientierten Klassen zum Einlesen von Daten und Writer die Oberklasse aller zeichenorientierten Klassen zum Schreiben von Daten. Da die meisten Betriebssysteme noch nicht den Unicode-Zeichsatz unterstützen, muss eine Konvertierung der Zeichen bei Ein-/Ausgaben erfolgen, die automatisch von den Unterklassen dieser Klassen, InputStreamReader und OutputStreamWriter, durchgeführt wird. Diese beiden Klassen stellen eine Verbindung zwischen den beiden vorher definierten Stream-Arten (byte- und zeichenorientiert) dar und werden in der Java-Literatur auch als Brückenklassen oder Adapterklassen bezeichnet. Für die Umwandlung der Daten wird eine Default-Kodierung benutzt, die der Kodierung des Zeichensatzes auf der verwendeten Betriebssystem-Plattform entspricht und automatisch von den Brückenklassen ermittelt wird. Parallel zur Default-Kodierung kann eine plattformabhängige Kodierung eingesetzt werden, welche über die Systemeigenschaft »file.encoding« abgefragt werden kann und dann im Konstruktor von Brückenklassen als String übergeben werden muss. Wie andere Systemeigenschaften auch, wird auch diese von der Java-StandardKlasse Properties zur Verfügung gestellt, deren Feld defaults aus Sicherheitsgründen mit dem Modifikator protected definiert ist. Über den Aufruf der Methode getProperty() der Klasse System kann der Name der Zeichenkodierung ermittelt werden. Über die Konstruktoren der Klassen InputStreamReader und OutputStreamWriter, die beide mehrfach überladen sind, wird ein Byte-Stream vom Typ InputStream über eine angegebene Charset- oder CharsetDecoder-Instanz in eine für die Reader-Klassen lesbare Instanz umgesetzt bzw. eine als Instanz der Klasse OutputStream übergebene Anzahl von Zeichen mit Angabe eines Zeichensatzes in Bytes umgewandelt. Die abstrakte Klasse Charset definiert eine Beziehung zwischen Folgen von 16-BitUnicode-Zahlen und Folgen von Bytes. Beispiele von Standard-Charsets in Java sind US-ASCII, ISO-8859-1 und UTF-8. In Java wird eine leicht modifizierte Version der Unicode Transformation Format (UTF)-8-Kodierung genutzt, mit welcher 16 Bit-Unicode-Zahlen auf 8 Bit-Zahlen abgebildet werden, die auch in Dateien abgespeichert und wieder zurückgewonnen 33 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams werden können. Die Verschlüsselung der Unicode-Zeichen erfolgt in Abhängigkeit von deren Wert. So werden die Unicode-Zeichen, die durch den ASCII-Zeichensatz von \u0001 bis \u007F repräsentiert werden, mit einem Byte kodiert, das UnicodeZeichen mit dem Wert \u0000 sowie die Zeichen, deren Wert zwischen \u0080 und \u07FF liegt, mit zwei Bytes und die Zeichen mit einem Wert von \u0800 bis \uFFFF mit drei Bytes kodiert. Die abstrakte Klasse CharsetDecoder kann eine Folge von Bytes mit dem spezifizierten Charset in eine Folge von 16-Bit-Unicode-Zahlen umwandeln. Für das Umsetzen von Byte-Folgen in Strings können auch die entsprechenden Konstruktoren der String-Klasse benutzt werden. Die Klasse CharsetDecoder sollte laut Java-Dokumentation dann genutzt werden, wenn eine zusätzliche Kontrolle beim Dekodieren erforderlich ist. Character-Streams sind einfacher zu benutzen und darum wird empfohlen, auf diese zurückzugreifen, wenn keine binären Daten verarbeitet werden sollen. Da eine sprachspezifische Zeichendarstellung nur beim Lesen und Schreiben von Texten eine Rolle spielt, ist es jedoch sinnvoll, ansonsten die byteorientierten Streams zu benutzen. Die Unterklassen der vorher beschriebenen Stream-Klassen werden im Nachfolgenden immer parallel dargestellt, da es in ihrer Funktionalität keine großen Unterschiede gibt. Der Datentyp, auf dem diese Streams operieren, ist zwar verschieden, aber sie definieren in fast allen Fällen die gleichen oder ähnliche Methoden. Die Unterklassen von InputStream und OutputStream Die abstrakten Klassen InputStream und OutputStream stellen dem Programmierer über die Objekte ihrer konkreten Unterklassen die wichtigsten Streams für die Ein- und Ausgabe von binären Daten zur Verfügung. Für das Schreiben und Lesen von binären Daten in (aus) eine (einer) Datei werden die Klassen FileInputStream und FileOutputStream verwendet. Die Bearbeitung der Daten aus den Dateien kann hiermit nur sequentiell erfolgen. Das Schreiben und Lesen von Bytes in (aus) den (dem) Arbeitsspeicher wird von den Klassen ByteArrayInputStream und ByteArrayOutputStream bewerkstelligt. Die abstrakten Unterklassen FilterInputStream und FilterOutputStream dienen der Umwandlung von Daten, um für diese neue Funktionalitäten zu ermöglichen. Deren Unterklassen BufferedInputStream und BufferedOutputStream dienen der Pufferung von Daten. Die Klasse StringBufferInputStream ermöglicht, Daten aus einem String mithilfe eines Streams vom Typ der Klasse zu lesen. Sie liest die Daten aus einem StringBuffer, der als Objekt einer gleichnamigen Klasse erzeugt wird. Methoden zum Schreiben und Lesen von primitiven Datentypen sind in den Schnittstellen DataOutput und DataInput spezifiziert, die von den Klassen Data- 34 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.2 Die Definition und Klassifikation von Streams OutputStream und DataInputStream (zwei weitere Unterklassen von FilterInputStream und FilterOutputStream) implementiert werden. Die Klassen ObjectInputStream und ObjectOutputStream ermöglichen ein vorher mithilfe von Instanzen der Klasse ObjectOutputStream gespeichertes Objekt durch eine Instanz vom Typ ObjectInputStream wieder einzulesen bzw. über das Netz zu transportieren. Dieser Vorgang ist unter dem Begriff Objektserialisierung bekannt. Mithilfe von print-Methoden der Klasse PrintStream (ebenfalls von FilterOutputStream abgeleitet) können primitive Datentypen in einem Textformat (mit und ohne Zeilenumbruch) ausgegeben werden. Hierfür werden die Methoden print(), println() und ab der Version 5.0 von Java die Methode printf() zur Verfügung gestellt. Der Vollständigkeit halber erwähnen wir an dieser Stelle auch die Klasse PushbackInputStream, die das Zurückschreiben eines bereits gelesenen Streams ermöglicht, und die Klassen PipedOutputStream und PipedInputStream, die für einen Datenaustausch zwischen Threads eingesetzt werden können. Die mit der Version 1.4 des JDK eingeführten Interfaces ImageOutputStream und ImageInputStream, welche die Interfaces DataOutputStream und DataInputstream erweitern, sowie die abstrakten Klassen ImageReader und ImageWriter können für Dekodierungsaufgaben beim Schreiben und Lesen von Bildern im Kontext der Java Image I/O API benutzt werden. Um konkrete Instanzen zu bilden, müssen diese Klassen erweitert werden. Auf die Piped-Streams werden wir in Kapitel 2 zurückkommen. Die Unterklassen von Reader und Writer Die Klassen FileWriter und FileReader sind nicht direkt von Writer und Reader abgeleitet, sondern von deren Unterklassen, den Brückenklassen OutputStreamWriter und InputStreamReader. Die Bearbeitung der Daten aus einer Datei kann auch in diesem Fall nur sequentiell erfolgen. Die Klassen CharArrayReader und CharArrayWriter können für das Lesen und Schreiben von char-Arrays genutzt werden und die Klassen StringReader und StringWriter für das Lesen und Schreiben von Strings. Um die Performance von Zugriffen auf Textdaten zu steigern, können die Klassen BufferedReader und BufferedWriter eingesetzt werden, welche ebenso wie die Klassen BufferedInputStream und BufferedOutputStream der Pufferung von Daten dienen, aber im Unterschied zu diesen nicht von den Filterklassen, sondern direkt von den Klassen Reader und Writer abgeleitet sind. Die Filterklassen für Textdaten, FilterReader und FilterWriter, sind abstrakte Klassen und ebenfalls direkte Erweiterungen der Klassen Reader und Writer. Mit Character-Streams sind auch formatierte Ausgaben möglich; dazu kann die Klasse PrintWriter benutzt werden. Sie ermöglicht mithilfe von print()-, 35 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams println()- und printf()-Methoden die Ausgabe einer String-Repräsentation für beliebige primitive Typen und Objekte. Streams, zu deren Erzeugung die Instanz einer anderen Stream-Klasse benötigt wird, werden auch als »abgeleitete Streams« bezeichnet. Sie erweitern den Stream, der im Konstruktor ihrer Klasse als Referenz übergeben wird, um weitere Funktionen oder filtern die Daten, die über diesen Stream transportiert werden. Dazu gehören alle von FilterOutputStream bzw. FilterWriter und FilterInputStream bzw. FilterReader abgeleitete Klassen. Wenn mit abgeleiteten Streams weitere abgeleitete Streams durch die Übergabe von deren Referenzen in Konstruktoraufrufen nacheinander gebildet werden, wird auch von Kettungen oder Verknüpfungen von Streams gesprochen. Wie die nachfolgenden Beispiele zeigen, sind viele Kettungen von Streams realisierbar. 1.3 Die gemeinsamen Methoden zum Schreiben und Lesen der Oberklassen OutputStream und Writer bzw. InputStream und Reader Die Methode zum Schreiben in einen OutputStream (bzw. Writer) ist write(), welche Bytes (bzw. Zeichen) in das Ziel hinterlegt. Die drei write()-Methoden, welche sich in der Klasse OutputStream überladen, nehmen einzelne Bytes vom primitiven Typ byte oder byte-Arrays entgegen. In den fünf überladenen Methoden der Klasse Writer werden einzelne Zeichen oder Arrays von Zeichen bzw. String- oder CharSequence-Objekte über ihre Referenzen übergeben. CharSequence ist ein Interface, das von Klassen wie String, CharBuffer und StringBuffer implementiert wird und eine lesbare Folge von char-Werten reprä- sentiert. Zum Zugriff auf Zeichenfolgen wird eine Indizierung der Zeichen vorgenommen, die wie bei Arrays mit 0 beginnt. Die im Methodenaufruf angegebenen Daten werden nicht sofort geschrieben, sondern erst zwischengespeichert. Ist der angegebene Schreibpuffer voll oder wird die Methode flush() der Klassen aufgerufen, werden die Daten in den AusgabeStream gelegt und der Schreibpuffer gelöscht. Die Methode zum Lesen eines InputStreams (bzw. Readers) ist read(), welche Bytes (bzw. Zeichen) von einer Quelle liest. Von den drei read()-Methoden, die sich überladen, werden in beiden Klassen einzelne Bytes bzw. Characters oder byte- bzw. char-Arrays zurückgeliefert. Der Rückgabewert der parameterlosen Methode gibt das gelesene Byte oder Zeichen zurück bzw. die Anzahl der gelesenen Zeichen für die restlichen read()-Methoden. Letzteren muss ein Puffer für das Speichern der eingelesenen Daten als Referenz im Methodenaufruf übergeben werden. Ist der Rückgabewert einer read()-Methode gleich -1, bedeutet dies, dass das Ende des Streams erreicht wurde. Die Methode skip() kann benutzt werden, um eine angegebene Anzahl Zeichen beim Lesen zu überspringen und reset(), um zu einer vorher über eine mit der 36 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.4 FileInputStream, FileReader, FileOutputStream, FileWriter Methode mark() markierten Position zurückzukehren. Der Aufruf von close() schließt den Stream. 1.4 Die Klassen FileInputStream und FileReader bzw. FileOutputStream und FileWriter und der Zugriff auf Dateien Die Instanzen dieser Klassen werden für das Lesen und Schreiben von Bytes und Zeichen aus (in) einer (eine) Datei genutzt. Wie bereits erwähnt, erfolgt der Zugriff auf die Datei damit immer sequentiell. Die Klassen FileInputStream und FileOutputStream definieren einen mehrfach überladenen Konstruktor, in dem ein Dateiname als String-Instanz, die Datei selbst als Objekt der Klasse File oder eine FileDescriptor-Instanz per Referenz übergeben werden können. Bei der Übergabe einer Datei oder ihres Namens wird eine FileNotFoundException geworfen, falls diese nicht vorhanden ist, welche abgefangen oder an die aufrufende Methode weitergereicht werden muss. Bei der Ein- und Ausgabe von Zeichen mithilfe der Character-Streams vom Typ FileReader und FileWriter erfolgt eine Konvertierung von Zeichen in Bytes und umgekehrt. Diese Arbeit wird von den Brückenklassen, von denen diese Klassen abgeleitet sind, übernommen. Die Klassen FileReader und FileWriter besitzen je drei Konstruktoren mit gleicher Syntax wie die byteorientierten FileStreams. Die Klassen FileOutputStream und FileWriter definieren noch zwei zusätzliche Konstruktoren mit einem Parameter vom Typ boolean, über den festgelegt werden kann, ob die zu schreibenden Daten zu den schon vorhandenen hinzugefügt werden oder diese vorher gelöscht werden sollen. Ist eine angegebene Datei nicht vorhanden, wird diese neu angelegt. Wird der boolean-Wert gleich false gesetzt oder ist dieser im Konstruktoraufruf nicht vorhanden, wird der vorherige Dateiinhalt beim Öffnen gelöscht. Aufgabe 1.5 Die Klassen FileOutputStream und FileInputStream Ein byteorientierter Stream vom Typ der Klasse FileOutputStream stellt Methoden zur Verfügung, die einzelne Bytes bzw. Arrays von Bytes in Dateien schreiben. Ein solcher Stream soll in einer Klasse ByteFileStreams mit einer Datei namens »Binärdatei« verbunden werden, die zuerst nicht vorhanden ist und bei Schreibzugriffen immer weiter fortgeschrieben werden soll. Initialisieren Sie ein byte-Array array mit den Werten, welche die 7-Bit ASCIIKey-Code Repräsentation der Buchstaben von A bis Z darstellen, und übergeben Sie dieses im Aufruf der write()-Methode. 37 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Ein byteorientierter Stream vom Typ der Klasse FileInputStream stellt Methoden zur Verfügung, die Bytes aus Dateien einlesen. Ein solcher Stream soll mit der vorher erstellten Datei verbunden werden, um die darin gespeicherten binären Daten zu lesen und am Bildschirm anzuzeigen. Java-Dateien: ByteFileStreams.java Programmaufruf: java ByteFileStreams Aufgabe 1.6 Die Klassen FileWriter und FileReader Erstellen Sie in Analogie zur Aufgabe 1.5 eine Klasse CharFileStreams, die Instanzen der Character-Stream-Klassen FileWriter und FileReader erzeugt und diesen im Konstruktoraufruf den String »Textdatei« als Dateiname übergibt. Auch damit sollen die Buchstaben A bis Z erstmals in die Datei geschrieben werden und danach aus der Datei gelesen werden. Schreiben Sie diesmal die Zeichen einzeln in die Datei und benutzen Sie zum Einlesen der Zeichen ein char-Array mit dem Namen array, dessen Elemente im Nachhinein am Bildschirm angezeigt werden sollen. Java-Dateien: CharFileStreams.java Programmaufruf: java CharFileStreams 1.5 Der Zugriff auf Daten aus dem Arbeitsspeicher mithilfe der Klassen ByteArrayOutputStream und CharArrayWriter bzw. ByteArrayInputStream und CharArrayReader Mit den drei write()-Methoden der Klasse ByteArrayOutputStream können Daten in Form eines ByteArray-Streams in einen Bereich des Arbeitsspeichers geschrieben werden, der mit der Anzahl von hinzugefügten Daten immer weiter wächst. Die in einem solchen Stream hinterlegten Daten können mit der Methode toByteArray() als byte-Array zurückgewonnen werden. Die Klasse überschreibt die Methode toString() der Klasse Object, um die im Stream enthaltenen Bytes nach den für die entsprechende System-Plattform spezifizierten Dekodierungsmethoden in Zeichen umzusetzen. Das Pendant dieser Klasse für Character-Streams ist die Klasse CharArrayWriter, welche auch die write()-Methoden ihrer Oberklasse überschreibt und eine äquivalente Methode toCharArray() für das Kopieren der im Stream eingegebenen Daten in ein char-Array definiert. Die Methode toString() liefert diese Daten als String zurück. 38 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.5 ByteArrayOutputStream, CharArrayWriter, ByteArrayInputStream, CharArrayReader Das Gegenstück der Klasse ByteArrayOutputStream ist die Klasse ByteArrayInputStream, deren Methoden für das Lesen der in einem ByteArray-Stream hinterlegten Daten aus dem Arbeitsspeicher zuständig sind. Sie überschreibt die von ihrer Oberklasse definierten Methoden read(), mark(), reset(), skip() und close(). Ihr Pendant, die von der Klasse Reader abgeleitete Klasse CharArrayReader, kann für das Lesen von Zeichen aus einem Character-Stream genutzt werden. Die Unterklassen StringWriter und StringReader der Writer- und ReaderKlassen sind den Klassen CharArrayWriter und CharArrayReader sehr ähnlich. Auch diese beiden Klassen überschreiben die Methoden ihrer Oberklassen und die Klasse StringWriter implementiert die zusätzliche Methode getBuffer(), die den Streaminhalt als Instanz der Klasse StringBuffer zurückliefert. Die Klasse StringBuffer liefert eine Thread-sichere modifizierbare Folge von Characters: D.h. ein StringBuffer ist ein modifizierbarer String, der kreiert wurde, um in Zusammenhang mit Multithreading (siehe das nachfolgende Kapitel) genutzt zu werden. Aufgabe 1.7 Die Klassen ByteArrayOutputStream und ByteArrayInputStream Definieren Sie in der main()-Methode einer Klasse ByteArrayStreams ein byteArray und initialisieren Sie seine Elemente mit den Zahlenwerten, welche die Codes der ASCII-Zeichen J, a, v, a darstellen (74, 97, 118, 97). Erzeugen Sie zwei Ausgabe-Streams vom Typ der Klasse ByteArrayOutputStream und schreiben Sie in den ersten die Zahlenwerte 49 bis 56, welche die Codes der ASCII-Zeichen 1 bis 6 darstellen, und in den zweiten die Werte der Elemente des vorher definierten byte-Arrays, jeweils mithilfe der write()-Methoden der Klasse. Die Größe eines ByteArray-Streams wächst nach Bedarf. Vergewissern Sie sich, dass einzeln geschriebene Bytes am Ende der vorher geschriebenen Arrayelemente nacheinander angefügt werden, indem Sie nach den Zeichen des Wortes »Java« auch noch die Zeichenkette »-Stream« anfügen (ASCII-Codes 45, 63, 116, 114, 101, 97, 109). Rufen Sie die Methoden writeTo() und toString() der Klasse ByteArrayOutputStream auf, um den Inhalt des zweiten Streams in den ersten zu kopieren und die Inhalte beider Streams für die Bildschirmanzeige in einen String zu konvertieren. Setzen Sie eine der ByteArrayOutputStream-Instanzen in ein byte-Array um, indem Sie die Methode toByteArray() der Klasse aufrufen, und übergeben Sie dieses bzw. eine beliebige Untermenge seiner Elemente in den Konstruktoren der Klasse ByteArrayInputStream, um zwei Instanzen dieser Klasse zu erzeugen. Lesen Sie aus diesen die gespeicherten Daten mithilfe von read()-Methoden der Klasse. 39 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Zeigen Sie die in (aus) die (den) Streams geschriebenen bzw. gelesenen Daten am Bildschirm an. Hinweise für die Programmierung: Die Erzeugung der Streams erfolgt im Arbeitsspeicher und im eigentlichen Sinne geht es darum, in/aus einen(m) Puffer durch dessen Umwandlung in/aus einen(m) Stream schreiben und lesen zu können. Erst bei einer Kettung mit anderen Streams kann die Existenzberechtigung derartiger Streams besser verdeutlicht werden (siehe dazu auch die Aufgabe 1.31). Weil das Schließen von ByteArray-Streams keine Auswirkung zeigt, braucht die close()-Methode dafür nicht aufgerufen zu werden. Weil einige der read/write-Methoden eine IOException werfen, müssen deren Aufrufe in einen try/catch-Block eingebettet werden. Java-Dateien: ByteArrayStreams.java Programmaufruf: java ByteArrayStreams Aufgabe 1.8 Die Klassen CharArrayWriter und CharArrayReader Definieren Sie analog zur Klasse ByteArrayStreams aus der vorigen Aufgabe eine Klasse CharArrayStreams, die Instanzen vom Typ der Klassen CharArrayWriter und CharArrayReader erzeugt und die gleichen Daten wie in der Aufgabe 1.7 von einer Quelle einliest und in ein Ziel schreibt. Achten Sie darauf, dass die Klasse CharArrayWriter einen dritten Konstruktor besitzt, der eine String-Referenz als Parameter definiert und erstellen Sie damit eine zusätzliche dritte Instanz der Klasse. Rufen Sie die gleichnamigen Methoden dieser Stream-Klassen für das Schreiben und Einlesen von Daten wie in der Aufgabe 1.7 auf und schließen Sie danach die Streams mit der close()-Methode. Java-Dateien: CharArrayStreams.java Programmaufruf: java CharArrayStreams 1.6 Die Filterklassen FilterInputStream und FilterReader bzw. FilterOutputStream und FilterWriter Die Filterklassen FilterInputStream und FilterOutputStream sind konkrete Klassen, die im Konstruktor eine Referenz vom Typ ihrer abstrakten Oberklasse übergeben bekommen und deren Methoden überschreiben. D.h. es können Instanzen von diesen Klassen gebildet werden, und die an diesen Instanzen aufgerufenen Methoden der Klassen geben alle Anforderungen über den im Konstruktor überge- 40 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.6 FilterInputStream, FilterReader, FilterOutputStream, FilterWriter benen InputStream an ihre Oberklasse weiter. Ihre Unterklassen definieren über ihre Felder und Methoden zusätzliche Funktionalitäten. Ihre Pendants unter den Character-Streams, die Klassen FilterReader und FilterWriter, verfügen über die gleiche Struktur, sind jedoch in Java als abstrakte Klassen definiert. Darum müssen diese Klassen erweitert werden, um konkrete Unterklassen zu bilden, deren Konstruktoren die Konstruktoren der Oberklasse und in ihren read()- und write()-Methoden die gleichnamigen Methoden der Oberklasse aufrufen. Soll der Einsatz von Instanzen von Filterklassen demonstriert werden, führt dies zu einem Ketten mit anderen Streams, weil mit Filter-Streams keine direkte Ein-/Ausgabe von Daten erfolgen kann, diese dienen lediglich einer Zwischenbehandlung von Ein-/Ausgaben. Die zu bearbeitenden Streams werden, wie bereits erwähnt, im Konstruktur der Filterklassen beim Instantiieren über ihre Referenzen übergeben, woher auch die Bezeichnung als »abgeleitete Streams« für diese Klassen hervorgeht. Aufgabe 1.9 Das Ketten von FilterOutputStreams mit FileOutputStreams Die Klasse FilterOutputStream definiert einen »Null-Filter«. Sie liest Daten von einem im Konstruktor übergebenen OutputStream und gibt diese unverändert zurück. Um eine gewünschte Filterung durchführen zu können, muss eine Unterklasse die write()-Methoden überschreiben, welche dazu erforderlich sind. Definieren Sie eine von FilterOutputStream abgeleitete Klasse UpperCaseOutputFilter, mit deren Instanzen ein Filter zum Umsetzen von Klein- in Großbuchstaben erzeugt werden kann. Überschreiben Sie dazu die write()-Methoden der Oberklasse zum Schreiben von einzelnen Bytes bzw. eines byte-Arrays und rufen Sie in diesen mit dem Präfix out. die write()-Methoden der Klasse auf, die im Konstruktor als Argument übergeben wird. Das Feld out ist in der Oberklasse mit protected definiert und damit für alle Unterklassen zugänglich. Die Klasse ByteOutputFilterfuerFileStreams soll zum Testen des Filters eingesetzt werden. Sie definiert ein byte-Array, dessen Elemente mit den Buchstaben von a bis z initialisiert werden. Erzeugen Sie einen FilterStream vom Typ der Klasse UpperCaseOutputFilter zum Umsetzen der Daten von Klein- in Großbuchstaben, die in einen FileOutputStream geschrieben werden sollen. Bilden Sie eine Instanz vom Typ der Klasse FileOutputStream, der im Konstruktor der Dateiname »Filter« übergeben wird und übergeben Sie eine Referenz auf diese im Konstruktor der Klasse UpperCaseOutputFilter. 41 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Rufen Sie an dem so erzeugten FilterStream die Methode write() auf, in der eine Referenz auf das vorher definierte byte-Array übergeben werden kann, um diese Daten in die Datei zu schreiben. Zwecks Bildschirmanzeige sollen die Daten mithilfe einer FileInputStreamInstanz über den Aufruf der read()-Methode eingelesen werden. Benutzen Sie im Methodenaufruf die gleiche Arrayreferenz wie beim Schreiben der Daten. Hinweise für die Programmierung: Die Kettung der Streams mithilfe der FileOutputStream-Instanz kann wie folgt erfolgen: UpperCaseOutputFilter byteFilterOut1 = new UpperCaseOutputFilter (new FileOutputStream("Filter")); und das Umsetzen von Klein- in Großbuchstaben in den überschriebenen write()-Methoden kann wie folgt durchgeführt werden: out.write(b-32); Java-Dateien: UpperCaseOutputFilter.java, ByteOutputFilterfuerFileStreams.java Programmaufruf: java ByteOutputFilterfuerFileStreams Aufgabe 1.10 Das Ketten von FilterOutputStreams mit der System.out-Instanz Die Klasse ByteOutputFilterfuerPrintStream aus dieser Aufgabe soll zum weiteren Testen des mit der Klasse UpperCaseOutputFilter definierten Filters eingesetzt werden. Sie definiert ebenfalls ein byte-Array, das mit den Buchstaben von a bis z initialisiert wird. Erzeugen Sie eine Instanz der Klasse UpperCaseOutputFilter, welche im Konstruktor die in der Klasse System definierte System.out-Instanz vom Typ der Klasse PrintStream (die standardmäßig der Konsole zugeordnet ist) übergeben bekommt. Testen Sie auch den von der Klasse FilterOutputStream definierten Null-Filter, indem Sie anstelle einer Referenz vom Typ UpperCaseOutputFilter eine Referenz vom Typ der FilterOutputStream-Klasse übergeben. Hinweise für die Programmierung: Für die Ausgabe der Daten am Bildschirm, die mit den FilterOutputStreams umgesetzt wurden, kann eine Kettung von Streams genutzt werden: FilterOutputStream byteFilterOut1 = new FilterOutputStream(System.out)); bzw. UpperCaseOutputFilter byteFilterOut2 = new UpperCaseOutputFilter (System.out); Java-Dateien: UpperCaseOutputFilter.java ByteOutputFilterfuerPrintStream.java Programmaufruf: java ByteOutputFilterfuerPrintStream 42 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.6 FilterInputStream, FilterReader, FilterOutputStream, FilterWriter Aufgabe 1.11 Das Ketten von FilterWriter-Streams mit der System.out-Instanz Die Klassen UpperCaseFilterWriter und CharOutputFilterfuerPrintWriter sollen das Pendant zu den Klassen aus der Aufgabe 1.10 für Character-FilterStreams darstellen. So wird die Klasse UpperCaseFilterWriter von FilterWriter abgeleitet, die ebenso wie ihre Writer-Oberklasse als abstrakt definiert ist. Die Klasse FilterWriter erbt die Methoden der Oberklasse, ohne diese neu zu implementieren, so dass dies den davon abgeleiteten Klassen überlassen bleibt. Implementieren Sie auch die write()-Methode, welche als Parameter eine String-Referenz definiert, um diese für das Schreiben von Strings in den Ausgabe-Stream zu nutzen. Erzeugen Sie in der main()-Methode der Klasse CharOutputFilterfuerPrintWriter eine Instanz der Klasse UpperCaseFilterWriter, die analog zur Aufgabe 1.10 im Konstruktor die System.out-Instanz, die per Voreinstellung eine Instanz der Klasse PrintStream ist, übergeben bekommt. Achten Sie darauf, dass diese Instanz über eine Referenz vom Typ der Klasse OutputStreamWriter übergeben werden muss, um eine Brücke zwecks Datenkonvertierung zwischen dem Byte-Stream vom Typ PrintStream und dem CharacterStream vom Typ FileWriter zu definieren. Übergeben Sie diese im Konstruktoraufruf der Klasse PrintWriter für das Umsetzen von Klein- in Großbuchstaben der Bildschirmausgaben. Hinweise für die Programmierung: Die in der Klasse UpperCaseOutputFilter implementierten write()-Methoden, welche eine Array- bzw. Stringreferenz als Parameter besitzen, definieren zwei zusätzliche Parameter vom Typ int, in denen Anfangsposition und Länge der zu schreibenden Daten übergeben werden. Außerdem erfolgt erst nach dem Schließen des PrintWriter-Streams eine Ausgabe am Bildschirm. Das Umsetzen von Klein- in Großbuchstaben in den überschriebenen write()Methoden kann mit Character.toUpperCase((char)b); durchgeführt werden, falls b einen int-Wert bezeichnet und mit Character.toUpperCase(c[i]); bzw. String.valueOf(c).toUpperCase(); für den Fall, dass c ein char-Array bezeichnet. Die Klasse FilterWriter ist eine abstrakte Klasse, von der keine Instanzen gebildet werden können, so dass der damit definierte Null-Filter nur über eine davon abgeleitete Klasse, die selbst einen Null-Filter definiert, getestet werden kann. Java-Dateien: UpperCaseFilterWriter.java, CharOutputFilterfuerPrintWriter.java Programmaufruf: java CharOutputFilterfuerPrintWriter 43 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Aufgabe 1.12 Das Ketten von FilterInputStreams mit FileInputStreams Die Klasse FilterInputStream wird von der Klasse InputStream abgeleitet und dient dem Filtern von Daten eines byteorientierten Eingabe-Streams. Sie liest Zeichen von einem im Konstruktor übergebenen InputStream und gibt diese unverändert zurück, so dass ihre Instanzen nur als »Null-Filter« eingesetzt werden können. Wie beim Schreiben, bleibt auch beim Lesen dem Programmierer selbst überlassen, wie er die Filterung von eingelesenen Daten den jeweiligen Anforderungen anzupassen hat. Deshalb muss eine Unterklasse von FilterInputStream die read()-Methoden der Oberklasse überschreiben und daraus die gleichnamigen Methoden der im Konstruktor übergebenenen Streams, die erforderlich sind, um eine gewünschte Filterung durchführen zu können, aufrufen. Definieren Sie eine von FilterInputStream abgeleitete Klasse LowerCaseInputFilter, mit deren Instanzen diesmal ein Filter zum Umsetzen von Groß- in Kleinbuchstaben erzeugt werden kann. Überschreiben Sie dazu die read()Methode der Oberklasse zum Einlesen eines byte-Arrays und rufen Sie in dieser mit dem Präfix in. die read()-Methode der Klasse auf, deren Instanzen im Konstruktor als Argument übergeben werden. Das Feld in ist in der Oberklasse mit protected definiert und damit für alle Unterklassen zugänglich. Die Klasse ByteInputFilterfuerFileStreams soll zum Testen des Filters eingesetzt werden. Ein byteorientierter Stream vom Typ der Klasse FileInputStream wird mit einer Datei namens »ByteFilter« verbunden und in diese Datei werden die 7-Bit ASCIIKey-Codes für die Buchstaben von von A bis Z geschrieben. Erzeugen Sie einen FilterStream vom Typ der Klasse LowerCaseInputFilter, bilden Sie eine Instanz vom Typ FileInputStream, der im Konstruktor der Dateiname »ByteFilter« übergeben wird und übergeben Sie diese im Konstruktor der Klasse LowerCaseInputFilter zum Einlesen der dadurch gefilterten, vorher geschriebenen Daten. Selbstverständlich können Sie zum Testen weitere beliebige Dateien nutzen. Hinweise für die Programmierung: Achten Sie beim Umsetzen der Daten darauf, dass die Summe von zwei byte-Werten nur als int-Wert ermittelt werden kann: int zahl = (new Byte(b[i])).byteValue() + (new Integer(32)).byteValue(); und dieser wiederum zurück konvertiert werden muss: b[i] = new Byte(""+zahl);, falls Sie diesen Weg für die Konvertierung gewählt haben. Java-Dateien: LowerCaseInputFilter.java, ByteInputFilterfuerFileStreams.java Programmaufruf: java ByteInputFilterfuerFileStreams 44 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.7 BufferedOutputStream, BufferedWriter, BufferedInputStream, BufferedReader Aufgabe 1.13 Das Ketten von FilterReader-Streams mit FileReader-Streams Wie schon angesprochen, ist das Pendant der Klasse FilterInputStream für Character-Streams die Klasse FilterReader, eine abstrakte Klasse, die ebenso wie die Klasse FilterWriter einen »Null-Filter« definiert. Sie liest Zeichen von einem angegebenen Reader und gibt sie unverändert zurück. D. h. sie erbt alle Methoden der Reader-Klasse, ohne diese neu implementieren zu müssen. Um eine gewünschte Filterung durchführen zu können, muss eine Unterklasse die read()Methoden, welche erforderlich sind, überschreiben. Definieren Sie in Analogie zur Aufgabe 1.12 eine von FilterReader abgeleitete Klasse LowerCaseFilterReader, mit deren Instanzen ein Filter zum Umsetzen von Groß- in Kleinbuchstaben erzeugt werden kann, um diesen beim Einlesen von Daten aus einem zeichenorientierten Stream einzusetzen. Implementieren Sie dazu die read()-Methode der Oberklasse FilterReader zum Einlesen von Zeichen in ein char-Array und rufen Sie in dieser mit dem Präfix in. die read()-Methode der Klasse auf, die im Konstruktor als Argument übergeben wird. Die Klasse CharInputFilterfuerFileReader soll zum Testen des Filters eingesetzt werden. In eine Datei namens »CharFilter« werden erstmals alle Buchstaben von A bis Z mithilfe eines FileWriter-Streams geschrieben. Sie können diese Aktion überspringen und eine schon vorhandene Datei zum Testen benutzen. Bilden Sie eine Instanz vom Typ LowerCaseFilterReader, die im Konstruktoraufruf eine FileReader-Instanz mit dem Dateinamen »CharFilter« als Argument übergeben bekommt, und rufen Sie an dem so erzeugten FilterReader-Stream seine read()-Methode auf. Zeigen Sie die aus dem FileReader-Stream eingelesenen Daten am Bildschirm an. Java-Dateien: LowerCaseFilterReader.java, CharInputFilterfuerFileReader.java Programmaufruf: java CharInputFilterfuerFileReader 1.7 Das Puffern von Daten und die Klassen BufferedOutputStream und BufferedWriter bzw. BufferedInputStream und BufferedReader Die Aufgabe aller Buffered-Klassen besteht darin, die zu lesenden bzw. zu schreibenden Daten zu puffern, um die Geschwindigkeit im Zugriff auf die Daten der zugrunde liegenden Streams zu beschleunigen. 45 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Auch die Instanzen dieser Klassen werden wie Instanzen von Filterklassen mit anderen Streams gekettet. Die Klassen BufferedOutputStream und BufferedWriter bzw. BufferedInputStream und BufferedReader verfügen über je zwei Konstruktoren, die den zu puffernden bzw. gepufferten Stream entgegennehmen, der über eine Referenz vom Typ ihrer OuputStream- bzw. InputStream-Oberklasse übergeben wird. Der in Java definierte »überladene Polymorphismus« macht möglich, dass auch Referenzen vom Typ der Unterklassen übergeben werden können. Im zweiten Konstruktor der Klassen kann auch die Größe des internen Puffers angegeben werden. Die Zwischenspeicherung der Daten erfolgt intern über ein byte- bzw. char-Array, dass für den Programmierer völlig transparent ist. Mithilfe der Methoden flush() und close() kann der Programmierer das Schreiben von Inhalten aus dem Puffer ansteuern, der ansonsten immer nur dann automatisch geleert wird, wenn er voll ist. Ein sinnvoller Einsatz von diesen Klassen bietet sich in Zusammenhang mit dem Zugriff auf Dateien an. Auch wenn viele einzelne Schreibzugriffe auf einen Stream erfolgen sollen, ist der Einsatz dieser Puffer zu empfehlen. In diesem Zusammenhang wollen wir auch explizit auf die Methoden readLine() bzw. newLine() der Klassen BufferedReader bzw. BufferedWriter hinweisen. Die Methode readLine() ermöglicht einen zeilenweisen Zugriff auf Textdaten und die Methode newLine() das Hinzufügen eines Separators für den Zeilenumbruch (das Zeilenende-Zeichen: ‚\n’ oder ‚\r\n’ je nachdem, auf welchem Betriebssystem das Programm läuft). Weil die readLine()-Methode ein zeilenweises Einlesen von Strings ermöglicht, was beim Einlesen von Daten von einer Konsole oder aus einer Datei von großer Bedeutung ist, ist sie eine der mit am häufigsten benutzten Methoden beim Einlesen von textorientierten Daten. Die von BufferedReader abgeleitete Klasse LineNumberReader überschreibt die read()- und readLine()-Methoden ihrer Oberklasse und definiert zwei zusätzliche Methoden setLineNumber() und getLineNumber(), mit deren Hilfe ein Zähler für Zeilen gesetzt bzw. abgefragt werden kann. Die read-Methoden dieser Klassen werfen eine Exception vom Typ IOException, wenn ein Ein-/Ausgabe-Fehler auftritt. Aufgabe 1.14 Das Ketten von BufferedInputStreams und BufferedOutputStreams mit FileInputStreams Um die Zeitdauer bei gepufferten und nicht gepufferten Dateizugriffen zu vergleichen, sollen die in der Klasse ByteFileStreams verwendeten FileStreams für den Zugriff auf Daten mit Buffered-Streams verknüpft werden. 46 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.7 BufferedOutputStream, BufferedWriter, BufferedInputStream, BufferedReader Definieren Sie dazu eine Klasse ByteFileundBufferedStreams, die in ihrer main()-Methode Instanzen vom Typ BufferedOutputStream und BufferedInputStream erzeugt und rufen Sie an diesen die read()- und write()-Methoden dieser Klassen auf. Lesen Sie mithilfe der Methode currentTimeMilles() der Klasse System beim Starten sowie nach dem Beenden von Dateizugriffen die aktuelle Zeit in Millisekunden und berechnen Sie die Zeitdauer der Zugriffe, indem Sie die Differenz der so ermittelten Werte bilden. Hinweise für die Programmierung: Wenn zwei Streams gekettet werden, soll immer die close()- Methode für den Stream, der sich an einen anderen kettet, aufgerufen werden (d.h. die Methode des äußeren Streams). Java-Dateien: ByteFileundBufferedStreams.java Programmaufruf: java ByteFileundBufferedStreams Aufgabe 1.15 Das Ketten von BufferedReader-Streams und BufferedWriter-Streams mit FileReader-Streams Definieren Sie eine Klasse CharFileundBufferedStreams als Pendant zur Klasse ByteFileundBufferedStreams aus der Aufgabe 1.14 für BufferedWriter- und BufferedReader-Streams. Da bei Ein- und Ausgaben für Character-Streams immer eine Umwandlung zwischen Bytes und Characters stattfindet, muss für diese Streams die Umsetzung nicht für jedes geschriebene bzw. gelesene Zeichen einzeln vorgenommen werden, sondern immer nur dann, wenn der Puffer voll ist. Darum wird eine Instanz der Klasse BufferedWriter auch von der Klasse PrintWriter intern genutzt. Java-Dateien: CharFileundBufferedStreams.java Programmaufruf: java CharFileundBufferedStreams Aufgabe 1.16 Die readLine()-Methode der Klasse BufferedReader und das Selektieren von Textzeilen Definieren Sie eine von BufferedReader abgeleitete Klasse SelektierenvonTextZeilen, welche die readLine()-Methode ihrer Oberklasse überschreibt und diese gleichzeitig mit super. aufruft, um die Daten aus dem Stream zeilenweise zu lesen. Der Konstruktor der Klasse definiert als Parameter eine Referenz vom Typ der Klasse Reader. Die mit dieser Methode eingelesenen Textzeilen sollen nach 47 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams dem ersten darin enthaltenen Buchstaben selektiert werden. Ist dies der Buchstabe E (wie Error), werden die entsprechenden Textzeilen an die aufrufende Methode returniert, ansonsten sollen diese einfach übersprungen werden. Zum Testen der Klasse SelektierenvonTextZeilen soll eine weitere Klasse SelektierenvonTextZeilenTest erstellt werden. Für das Selektieren der Einträge aus einer Datei mit dem Namen »Meldungsdatei« wird eine FileReaderInstanz gebildet, die als Argument im Konstruktor der Klasse SelektierenvonTextZeilen übergeben wird. Schreiben Sie mit einem Editor in die »Meldungsdatei« mehrere Textzeilen, die mit den Buchstaben E oder I beginnen und Meldungen wie »E Datei nicht gefunden«, »E Ein-/Ausgabe-Fehler«, »I Datei wurde bearbeitet«, etc. beinhalten. Die aus der Datei gelesenen Fehlermeldungen sollen über die Kettung einer FileWriter-Instanz mit einem BufferedWriter-Stream in eine Datei namens »Fehlerdatei« geschrieben werden. Über den Aufruf der Methode newLine() an dem BufferedWriter-Objekt kann das Ende für Zeilen gesetzt werden. Sehen Sie sich den Inhalt der so erzeugten Datei auf Kommandozeilenebene mit dem Befehl type Fehlerdatei an. Java-Dateien: SelektierenvonTextZeilen.java, SelektierenvonTextZeilenTest.java Programmaufruf: java SelektierenvonTextZeilenTest 1.8 Die Schnittstellen DataOutput und DataInput und deren Methoden zum Schreiben und Lesen von primitiven Datentypen Wie schon erwähnt, sind die Methoden der Klassen zum Lesen und Schreiben von primitiven Datentypen DataOutputStream und DataInputStream in den Schnittstellen DataOutput und DataInput spezifiziert, welche von diesen Klassen implementiert werden. Die mithilfe von Methoden der Klasse DataOutputStream geschriebenen Daten werden so abgelegt, dass diese problemlos von den Methoden der Klasse DataInputStream wieder zurückgewonnen werden können. Die meisten Methoden der Klassen tragen den Namen des übergebenen primitiven Datentyps. Sie schreiben und lesen entweder einen Wert vom angegebenen Typ wie z.B. readBoolean(), writeBoolean(), readInt(), writeInt() etc. oder ganze Zeichenketten in Unicode- oder UTF-8-Format (mit /n bzw. /n/r beendet für das Lesen) wie z.B. readLine(), readUTF(), writeBytes(), writeChars() und writeUTF(). Die mit readLine() eingelesenen Textzeilen werden automatisch in einen String konvertiert, wobei aber nicht die unterschiedlichen Zeichensätze auf den verschiedenen System-Plattformen berücksichtigt werden. Diese Aufgabe wird von der Klasse BufferedReader übernommen, so dass eine Kettung mit einem Stream 48 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.8 Die Schnittstellen DataOutput und DataInput vom Typ dieser Klasse, welche auch eine readline()-Methode definiert, sich als durchaus sinnvoll erweisen kann. Die Methoden writeChars() und readChar() arbeiten mit Unicode-Zeichen. Es werden damit immer zwei Bytes pro Zeichen geschrieben und gelesen. Die Methode writeUTF() speichert den String nicht im 2-Byte-Unicode-Format, sondern in dem in der theoretischen Einführung beschriebenen modifizierten UTF-8Format, in dem die Unicode-Zeichen mit einer Sequenz von ASCII-Zeichen dargestellt werden. Darum muss bei einer Nutzung dieser Klassen immer darauf geachtet werden, dass die parallel eingesetzten Schreib- und Lesemethoden auf dem gleichen Datentyp operieren. Auf die Klasse DataOutputStream soll nur dann zurückgegriffen werden, wenn mit unterschiedlichen Datentypen gearbeitet werden soll. Für die Speicherung in Form von Texten sind Streams vom Typ der Klassen PrintStream und PrintWriter dieser Art von Streams wegen des einfacheren Umgangs vorzuziehen. Von allen readxxx-Methoden für primitive Datentypen und von der readUTF()Methode für das Lesen von Strings als auch von den readFully()-Methoden wird eine Ausnahme vom Typ EOFException erzeugt, falls das Ende des Streams erreicht wurde. Die readline()-Methode liefert den Wert null zurück und die read()-Methoden von Bytes den Wert -1. Wie im Fall von Buffered- und Filter-Klassen, sind auch die Instanzen von diesen Klassen dazu prädestiniert, mit anderen Streams gekettet zu werden. So kann durch die Kettung mit FileOutput- und FileInput-Streams, die nur auf ByteEbene arbeiten, gewährleistet werden, dass int-, float-, double-Werte sowie Werte von anderen primitiven Datentypen manipuliert werden können. Gleichzeitig kann mithilfe der Methoden dieser Klassen das Speichern und Zurückgewinnen von primitiven Datentypen in und aus Dateien wesentlich vereinfacht werden. DataOutputStream- und DataInputStream-Objekte können aber nicht direkt mit Objekten vom Typ FileWriter und FileReader oder PipedWriter und PipedReader gekettet werden. Eine Kettung von mehreren Streamtypen ist bei der Verwendung dieser Streams für das Schreiben und Einlesen von Daten meistens auch deswegen sinnvoll, weil dadurch ein Beitrag zur Erhöhung der Performance geleistet werden kann. Aufgabe 1.17 Das Schreiben und Lesen von primitiven Datentypen Definieren Sie eine Klasse DataOutputundInputStreams, die in ihrer main()Methode lokale Variablen vom Typ aller primitiver Datentypen definiert und initialisiert. In dieser Klasse soll ein byteorientierter Stream vom Typ der Klasse FileOutputStream mit einer Datei namens »DateimitprimitivenDatentypen« verbunden und 49 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams über einen BufferedOutputStream mit einem DataOutputStream gekettet werden. Schreiben Sie mithilfe von gleichnamigen Methoden die Werte der entsprechenden primitiven Datentypen in die Datei und definieren Sie zusätzlich zwei StringReferenzen, um diese mit den unterschiedlichen write()-Methoden und der Methoden writeChars() und writeUTF() in die gleiche Datei zu schreiben. Ein byteorientierter Stream vom Typ der Klasse FileInputStream wird mit der vorher erstellten Datei verbunden und mit einem DataInputStream und einem BufferedInputStream gekettet, um die geschriebenen Daten daraus einzulesen und am Bildschirm anzuzeigen. Benutzen Sie die Methode readFully(), um alle geschriebenen Daten einzulesen, oder die Pendants der read()-Methoden zu den vorher aufgerufenen write()Methoden, um die Daten einzeln einzulesen (ansonsten muss der Stream für ein wiederholtes Lesen neu geöffnet werden). Für das Lesen der mit writeChars() gespeicherten Strings soll die Methode readline() der Klasse DataInputStream genutzt werden und für das Lesen des UTF-Strings die von der Klasse überladene Methode readUTF(). Hinweise für die Programmierung: Dadurch, dass die readline()-Methode der Klasse DataInputStream nicht immer korrekt die Bytes in Character konvertiert, wurde diese als »deprecated« gekennzeichnet. In der Online-Dokumentation von Java wird empfohlen, für das Lesen von Textzeilen die Methode readline() der Klasse BufferedReader zu benutzen. Java-Dateien: DataOutputundInputStreams.java Programmaufruf: java DataOutputundInputStreams Aufgabe 1.18 Das Filtern von primitiven Datentypen Die Klassen DataOutputStream und DataInputStream können erweitert werden. Dadurch, dass alle ihre Methoden als final definiert sind, können diese jedoch nicht überschrieben werden. Definieren Sie eine Klasse UmlauteSuchen, die alle Wörter mit Umlauten aus einer Datei über einen DataInputStream selektiert. Schreiben Sie mithilfe der Methode writeUTF() der Klasse DataOutputStream mehrere Strings mit und ohne Umlaut in eine Datei namens »DateimitUmlauten«. Initialisieren Sie in der main()-Methode der Klasse ein String-Array namens worte mit den von Ihnen ausgewählten Strings und ein weiteres namens umlaute 50 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.9 Formatierte Ein-/Ausgaben mit allen Umlauten aus dem deutschen Alphabet. Die lokale String-Referenz buchstaben soll mit der Zeichenkette »äöüßÄÖÜ« initialisiert werden. Erzeugen Sie mithilfe der Kettung DataOutputStream byteOut1 = new DataOutputStream(System.out) einen DataOutputStream und rufen Sie an diesem die Methode writUTF(buchstaben) auf, um die Umlaute auf die Konsole zu schreiben. Analog zur Klasse DataOutputundInputStreams aus der Aufgabe 1.17 soll auch in dieser Klasse ein byteorientierter Stream vom Typ der Klasse FileOutputStream mit der Datei namens »DateimitUmlauten« verbunden und ein DataOutputStream über einen byteorientierten Stream vom Typ der Klasse BufferedOutputStream mit diesem Stream gekettet werden. Zum Einlesen der Daten wird ein byteorientierter Stream vom Typ der Klasse FileInputStream mit der vorher erstellten Datei verbunden und ebenfalls mit einem DataInputStream und einem BufferedInputStream gekettet. Die gelesenen UTFStrings sollen auf Umlaute untersucht werden und alle, die Umlaute enthalten, sollen zusammen mit dem Unicode-Code-Point (gleich dem 7Bit ASCII-Key-Code) für den gefundenen Umlaut am Bildschirm angezeigt werden. Hinweise für die Programmierung: Der Unicode-Code-Point, der dem Zeichen auf Position i aus einer Zeichenkette entspricht, kann wie bereits erwähnt mit der Methode codePointAt(i) der Klasse String ermittelt werden. Lesen Sie die Datei »DateimitUmlauten« mit einem Hexa-Editor wie z.B. DF HEXEditor 1.1 ein, um die hexadezimale Verschlüsselung von Umlauten nachzuvollziehen (die Kodierung für ä Ä ü Ü ö Ö ß ist C4 E4 DC FC D6 F6 DF). Java-Dateien: UmlauteSuchen.java Programmaufruf: java UmlauteSuchen 1.9 Formatierte Ein-/Ausgaben Wie so oft in Java (und in vielen anderen Programmiersprachen) sind Begriffe so eng miteinander verknüpft, dass die richtige Reihenfolge in ihrer Behandlung manchmal nicht einfach zu finden ist. So sieht es auch mit der formatierten Aufbereitung von Daten aus. Werden Daten für eine Ausgabe formatiert, kann man sich das Ergebnis dieser Formatierungen nur über die Methoden von Print-Klassen ansehen. Umgekehrt benötigt man die mithilfe von Formatierungsklassen erworbenen Kenntnisse über Format-Spezifizierer, um Daten mit den Methoden von Print-Klassen auszugeben. Wie schon in den Beispielen zur Demonstration des Einsatzes von Filterklassen erwähnt wurde, stehen für die formatierte Ausgabe von Daten die Klassen Print- 51 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Stream und PrintWriter zur Verfügung. Sie definieren mehrere print()- und println()-Methoden, die sich überladen und einen primitiven Datentyp, ein char-Array oder eine String- bzw. Object-Referenz entgegennehmen können. Im Unterschied zu print()-Methoden, die kein Zeilenende setzen, fügen die println()-Methoden einen Zeilenumbruch hinzu. Die println()-Methoden liefern, wie auch die Methode newLine() der Klasse BuferredWriter, ein betriebs- systemabhängiges Zeilenende-Zeichen, das über die Systemeigenschaft (»system property«) line.separator definiert ist und verschieden von ‚\n’ sein kann. Die readLine()-Methode der Klasse BuferredReader funktioniert ihrerseits mit Zeilenenden von verschiedenen Betriebssystemen. In den Konstruktoren beider Klassen kann eine OutputStream- oder WriterInstanz übergeben werden, aber auch eine Datei als String- bzw. File-Referenz. Beide Klassen besitzen je zwei Konstruktoren mit einem zusätzlichen Parameter vom Typ boolean, der mit dem Namen autoFlush bezeichnet ist und dessen Wert intern ausgewertet wird. Wird der Wert dieses Parameters gleich true gesetzt, erfolgt sofort eine über print()- und println()-Methoden angestossene Ausgabe, ohne dass abgewartet wird, bis ein dafür eingesetzter Puffer vollgeschrieben wurde. Der Wert dieses Parameters kann nur über einen Konstruktoraufruf eingestellt werden und nachdem eine PrintWriter-Instanz erzeugt wurde, auch nicht mehr abgeändert werden. Wir werden die Auswirkung dieses Parametres auf Ausgaben im nächsten Kapitel in Thread-Klassen beobachten. Für eine Formatierung von Daten für die Ausgabe wurden mit der Version 5.0 von Java in der Klasse PrintStream die Methoden append(), format() und printf() hinzugefügt, für welche auch die Bemerkungen bzgl. des autoFlushParameters gelten. Die Methode append() fügt ein Zeichen bzw. eine Zeichenkette der Ausgabe hinzu und bekommt diese als Referenz vom Typ der Klasse CharSequence übergeben. Die in der Methode format() übergebenen Argumente vom Typ Object ersetzen die in der Zeichenkette enthaltenen Platzhalter, welche den Formatierungsstring definiert und als Referenz vom Typ String übergeben wird. Mit printf() wird ein formatierter String unter Nutzung des im Methodenaufruf spezifizierten Formats und der übergebenen Argumente ausgegeben. Die Klasse PrintWriter definiert mit Java 5 auch die zusätzlichen gleichnamigen Methoden append(), format() und printf() wie die Klasse PrintStream. Diese Methoden werden auf die gleiche Weise eingesetzt. Formatierte Ausgaben von Daten können auch mithilfe der Klasse java.util. Formatter erreicht werden, die in ihren Konstruktoren Stream- und File-Instanzen entgegennehmen kann. Diese Klasse wurde mit der Version 5.0 von Java eingeführt und definiert zwei format()-Methoden, die sich überladen und über die gleiche Parameterliste wie die Print-Klassen verfügen, aber anstelle einer PrintStream- bzw. PrintWriter-Instanz ein Formatter-Objekt zurückliefern. 52 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.9 Formatierte Ein-/Ausgaben Das Gegenstück zu Formatter ist die Klasse Scanner. Im Konstruktor der ebenfalls mit der Version 5.0 eingeführten Klasse kann eine InputStream- oder String-Referenz sowie eine Referenz von allen Objekten, deren Klassen die Schnittstellen Readable und ReadableByteChannel implementieren, darunter auch die FileReader-Klasse, übergeben werden. Die Klasse Scanner wird genutzt, um formatierte Eingaben in einen binären Datenstrom umzusetzen. Mit Ihrer Hilfe werden Zeichen aus einem Stream eingelesen, und sie stellt Methoden wie nextInt(), nextLong(), etc. für das Einlesen von primitiven Datentypen und Strings zur Verfügung. Der Typ der eingelesenen Daten kann im Vorhinein mit den Methoden hasNextInt(), hasNextDouble(), etc. sowie für Strings hasNext() über ein im Methodenaufruf übergebenes Suchmuster geprüft werden. Beide Klassen befinden sich im Paket java.util und sind als final deklariert, d.h. diese Klassen können nicht erweitert werden. Benutzen Sie ein Referenzbuch von Java oder die sehr detaillierte Beschreibung aus der Online-Dokumentation zur Klasse Formatter, um die Vielzahl von Format-Spezifizierern und deren Einsatzmöglichkeiten in der Konvertierung von Daten zu erforschen. Wir wollen in den folgenden Aufgaben nur auf die am häufigsten verwendeten eingehen. Mit den Format-Spezifizierer können die zugehörigen Argumente auf drei Arten referenziert werden: 쐽 Eine »explizite Indizierung« wird genutzt, wenn die Format-Spezifizierer einen Index für die Argumentenspezifikation enthalten, der als ganzzahliger Wert die Position des Argumentes in der Argumentenliste bestimmt: So führt z.B. die Formatierung formatter.format("%4$s %3$s %2$s %1$s %4$s %3$s %2$s %1$s", "1", "2", "3", "4"); zur String-Ausgabe »4 3 2 1 4 3 2 1«. 쐽 Von einer »relativen Indizierung« wird gesprochen, wenn der Format-Spezifizierer das Zeichen »<« beinhaltet, welches bewirkt, dass das Argument des vorangegangenen Format-Spezifizierers wiederholt genutzt wird. Existiert ein solches nicht, wird die Ausnahme MissingFormatArgumentException geworfen. So erfolgt mit der Formatierung formatter.format("%s %s %<s %<s", "1", "2", "3", "4"); die String-Ausgabe »1 2 2 2«. Die Strings »3« und »4« werden ignoriert, weil diese nicht referenziert wurden. 쐽 Mit dem Ausdruck »normale Indizierung« wird eine Indizierung bezeichnet, falls der Format-Spezifizierer weder das Zeichen »<« noch einen Index für die Argumentenspezifikation beinhaltet. Dem Format-Spezifizierer wird in diesem Fall ein sequentieller impliziter Index zugeordnet. So führt die Formatierung formatter.format("%s %s %s %s", "1", "2", "3", "4"); zur Ausgabe »1 2 3 4«. All diese Formen der Indizierung können nach Belieben kombiniert werden. Mit formatter.format("%2$s %s %<s %s", "1", "2", "3", "4"); wird der String »2 53 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams 1 1 2« ausgegeben. »3« und »4« werden ignoriert, weil diese nicht referenziert wurden. Die maximale Anzahl von Argumenten ist durch die maximale Dimension eines Java-Arrays limitiert, die über die JVM-Spezifikation von Java definiert wird. Die Klassen PrintStream und PrintWriter besitzen kein Pendant im Bereich der Eingabe-Streams für das Einlesen von formatierten Daten; dazu kann, wie schon angesprochen, die Klasse Scanner genutzt werden. 1.10 Standard Ein-/Ausgabe-Kanäle Die Kommandozeilen-Schnittstelle von Betriebsystemen unterstützt einen Mechanismus, der mit dem Namen Standard-Ein-/Ausgabe (»standard I/O«) bezeichnet wird. Die Standard-Ein-/Ausgabe besteht aus drei Kanälen, die in Java als Streams betrachtet werden: Standard-Input-, Standard-Output- und Standard-Error-Kanal. Diese Kanäle sind in der Regel über einen Gerätetreiber der Tastatur bzw. dem Bildschirm oder dem Drucker zugeordnet. Die drei Java-Standard-Streams, welche über die globalen statischen Referenzen in, out und err in der Klasse java.lang.System definiert werden, sind, wie bereits schon durch deren Benutzung in den vorangegangenen Aufgaben festgestellt werden konnte, Instanzen der Klassen java.io.InputStream und java.io.PrintStream. So müssen diese im Gegensatz zu allen anderen Streams nicht explizit vom Programm erzeugt werden. Dadurch, dass die Klasse System im Paket java.lang enthalten ist, stehen diese Objekte jeder Applikation von Anfang an zur Verfügung. Über die Referenz System.out können die Methoden der Klasse PrintStream für Standard-Ausgaben (in der Regel auf die Konsole) aufgerufen werden. Die Ausgabe auf den Standard-Fehlerkanal kann über die System.err-Referenz erfolgen und ist standardmäßig auch der Konsole zugeordnet. Über einen Programmaufruf, gefolgt von einem »>«-Zeichen und dem Dateinamen, kann der Standard-Output-Kanal in eine Datei oder auf den Drucker umgelenkt werden. Im Gegensatz dazu kann unter Windows der Standard-Error-Kanal nicht auf Kommandozeilenebene umgelenkt werden (wie z.B. unter Unix und Linux). Die Instanz der Klasse InputStream, auf welche die System.in-Referenz zeigt, stellt dem Programmierer nur read()-Methoden zur Verfügung, die Eingaben in einzelne int-Werte oder ein byte-Array speichern. D.h. Tastatureingaben können mit dieser Instanz nicht direkt in einen String eingelesen werden, sondern müssen in einen solchen umgewandelt werden, ebenso wie die einzelnen int-Codes, die über ein Casting mit (char) in das eingelesene Zeichen umgesetzt werden können. Weil diese Vorgehensweisen recht umständlich sind, werden die Streams zum Lesen von Tastatureingaben oft gekettet. Die nachfolgenden Beispiele zeigen, wie 54 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.10 Standard Ein-/Ausgabe-Kanäle nacheinander eingelesene Daten für die Weiterverarbeitung in Strings umgewandelt werden können. Mit Java 6 wurde in der Java-Standard-Klasse System die Methode console() hinzugefügt. Sie liefert ein Objekt vom Typ der Klasse Console zurück, die ebenfalls mit der Version 6.0 implementiert wurde. Diese Klasse befindet sich im Paket java.io.package und definiert Methoden zum Schreiben und Lesen auf und von einer zeichenorientierten Konsole, die einer Java Virtual Machine zugeordnet ist, wenn eine solche überhaupt existiert. Ob eine JVM eine Konsole besitzt, ist abhängig von der darunterliegenden System-Plattform und der Art und Weise, wie sie gestartet wird. Wenn die JVM über die Kommandozeile mithilfe des java-Kommandos gestartet wird und dabei die Output- und Input-Streams nicht umgelenkt werden, ist eine Konsole definiert und diese ist standardmäßig mit dem Bildschirm und der Tastatur, an welchem(r) die JVM wartet, verbunden. Weil die Klasse Console als final definiert ist, kann diese nicht erweitert werden und die einzige Instanz vom Typ dieser Klasse muss über den Aufruf der statischen Methode console() der Klasse System erzeugt werden. Die Methode readLine() dieser Klasse liefert eine von der Tastatur eingelesene Zeile zurück, die nicht durch ein Zeilenende-Zeichen abgeschlossen wurde, und null, falls das Ende des Streams erreicht ist. Ihre printf()-Methode führt eine korrekte Umsetzung von Bytes in Characters durch, so dass auch Umlaute (und andere sprachspezifische Zeichen) korrekt am Bildschirm dargestellt werden können (siehe die Aufgaben 1.34 und 1.35). Aufgabe 1.19 Die Klasse Formatter und das Formatieren von Ausgaben Testen Sie die normale, explizite und relative Indizierung in der Referenzierung von Formatierungsargumenten mithilfe von Format-Spezifizierern in einer Klasse ZahlenCharacterundStringFormatierung. Deklarieren und initialisieren Sie dazu mehrere Variablen von primitiven Datentypen, auch Arrayvariablen. Erzeugen Sie mithilfe des parameterlosen Konstruktors der Klasse Formatter ein Objekt dieser Klasse und rufen Sie an diesem seine format()-Methode auf, um die Daten für die Ausgabe am Bildschirm zu formatieren. Die Anzeige der Daten soll über den Aufruf der Methode System.out.println() erfolgen, der als Argument die Formatter-Instanz als Referenz übergeben werden kann. Hinweise für die Programmierung: Benutzen Sie für String-Ausgaben die Format-Spezifizierer %s oder %S und für char-Daten die Format-Spezifizierer %C oder %c. Zeigen Sie byte- und short- 55 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Werte als hexadezimale bzw. oktale Zahlen mit den Format-Spezifizierer %x (%X) und %o an. Mit dem Format-Spezifizierer %d kann eine beliebige Dezimalzahl angezeigt werden, %E steht für eine Anzeige von Gleitkommazahlen in wissenschaftlicher Schreibweise (mit Exponent) und %b bzw. %B für den Typ boolean. Benutzen Sie diese Format-Spezifizierer und viele andere Ihrer Wahl, um eine Formatierung der Daten vor der Ausgabe zu testen. Java-Dateien: ZahlenCharacterundStringFormatierung.java Programmaufruf: java ZahlenCharacterundStringFormatierung Aufgabe 1.20 Die Klasse Scanner und das Suchen, Ersetzen und Zerlegen von Zeichenketten Die Instanzen der Klasse Scanner sind einfache Text-Scanner, die primitive Datentypen und Strings mithilfe von regulären Ausdrücken umwandeln können. Reguläre Ausdrücke sind Zeichenfolgen, die als Suchmuster verwendet werden und als Strings oder als Instanzen der Java-Standard-Klasse Pattern angegeben werden können. Initialisieren Sie in der von Ihnen definierten Klasse SuchenundZerlegenvonZeichenketten drei verschiedene Stringvariablen mit den vorgegebenen Werten charString = "A a B b C c D d E e F f G g", intString = "1 2 3 4 5 6 7 8 9" und doubleString = "1,2 34,5 678,910". Übergeben Sie diese nacheinander im Konstruktor der Scanner-Klasse, um ihren Inhalt mit den Methoden hasNext(), hasNextInt() und hasNextDouble() zu prüfen und mit den Methoden next(), nextInt() und nextDouble() in einzelne Strings, int- und double-Werte zu zerlegen. Zeigen Sie die so ermittelten Werte am Bildschirm an. Erweitern Sie die Zeichenketten charString und intString mit den Sonderzeichen »$« und »&« und fügen Sie diese als einen neuen String zusammen, der zur Untersuchung im Konstruktor der Klasse Scanner übergeben wird. An der so erzeugten Scanner-Instanz soll die Methode useDelimiter() aufgerufen werden, die als Argument ein Suchmuster übergeben bekommt. Im Suchmuster kann mithilfe von Zeichenklassen eine Liste von Zeichen hinterlegt werden, die im Text vorkommen dürfen. Testen Sie die Ausgaben mit einigen der gebräuchlisten Zeichenklassen, wie »\\D« für eine beliebige Ziffer und »\\W« für ein Wortzeichen. Definieren Sie auch eigene Muster wie z.B. »\\$|\\&«, welche direkt im Methodenaufruf übergeben werden können, aber auch als Referenz eines Objektes der Klasse Pattern. Diese Klasse besitzt keine Konstruktoren und die Instanzen der Klasse werden über ihre Klassenmethode compile() erzeugt, die als Argument den entsprechenden regulären Ausdruck entgegennimmt. 56 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.10 Standard Ein-/Ausgabe-Kanäle Nutzen Sie die Methode matches() der Klasse Pattern, um nach bestimmtem Zeichenfolgen in einem Text zu suchen. Diese Methode returniert ein MatcherObjekt, das den Vergleich des Suchmusters mit der im Aufruf der Methode übergebenen Zeichenkette durchführt. Ersetzen Sie mithilfe der Methode replaceAll() der Klasse Matcher im Text »PatternMatcherScanner« alle Buchstaben von A bis M und a bis m mit dem Zeichen ! und fügen Sie im gleichen Text in einem zweiten Anlauf mit der Methode appendReplacement() der Klasse zu den Buchstaben a und e ein ! hinzu. Hinweise für die Programmierung: Die Klassen Pattern und Matcher befinden sich im Paket java.util.regex. Mit der Methode find() der Klasse Matcher kann auf die nächste Zeichenfolge, die dem angegebenen Suchmuster entspricht, zugegriffen werden. Ihre Methode group() returniert eine vorher ermittelte Zeichenfolge aus dem übergebenen Text. Java-Dateien: SuchenundZerlegenvonZeichenketten.java Programmaufruf: java SuchenundZerlegenvonZeichenketten Aufgabe 1.21 Die System.out-Instanz der Klasse PrintStream Definieren Sie eine Klasse ZahlenMatrixmitprintln, in der für alle natürlichen Zahlen von 1 bis n deren Potenzen von 1 bis m berechnet werden und in der Form 1**1 = 1, 2**3 = 8, usw. mittels einer nxm Matrix am Bildschirm angezeigt werden. Analog zu der Vorgehensweise aus der Aufgabe 1.19 sollen mithilfe einer Instanz der Klasse Formatter und dem Aufruf deren format()-Methode die Elemente der Matrix nach Ihren Vorstellungen formatiert werden. Die so erzeugte Formatter-Instanz wird im Aufruf der Methode println() am immer geöffneten Output-Stream System.out vom Typ der Klasse PrintStream für die Anzeige der Daten übergeben. Hinweise für die Programmierung: Für das Berechnen von Potenzen kann die Klassenmethode pow() der Java-Standard-Klasse Math aufgerufen werden. Hinweise zur Programmausführung: Der Standard-Output-Kanal kann auf Kommandozeilenebene in eine Datei oder auf den Drucker umgelenkt werden. Über den Programmaufruf java ZahlenMatrixmitprintln > ZahlenMatrix werden die Programmausgaben in eine Datei mit dem Namen »ZahlenMatrix« geschrieben. Sehen Sie sich den Inhalt dieser Datei mit dem Befehl type auf Kommandozeilenebene an. Java-Dateien: ZahlenMatrixmitprintln.java Programmaufruf: java ZahlenMatrixmitprintln 57 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Aufgabe 1.22 Andere PrintStream-Instanzen In einen PrintStream kann auch mit einem Formatter-Objekt eine Ausgabe erfolgen. Erzeugen Sie in Analogie zur Klasse ZahlenMatrixmitprintln aus der Aufgabe 1.21 eine Klasse ZahlenMatrixmitFormatter, in der im Konstruktor der Klasse Formatter eine PrintStream-Instanz übergeben wird. Für die Ausgabe muss dann keine print- oder write-Methode mehr aufgerufen werden. Java-Dateien: ZahlenMatrixmitFormatter.java Programmaufruf: java ZahlenMatrixmitFormatter Aufgabe 1.23 Die printf()-Methode Definieren Sie eine Klasse ZahlenMatrixmitprintf zum Testen von Ausgabeformatierungen mit der Methode printf() anstelle von format(). Erzeugen Sie eine PrintStream-Instanz, um daran die Methode printf() aufzurufen. Definieren Sie die Parameterliste der printf()-Methode analog zu der aus der format()-Methode aus den vorangegangenen Aufgaben. Hinweise für die Programmierung: Weil System.out eine Instanz der Klasse PrintStream ist, kann die printf()Methode auch an dieser aufgerufen werden, ohne dass eine neue Instanz der Klasse PrintStream erzeugt werden muss. Java-Dateien: ZahlenMatrixmitprintf.java Programmaufruf: java ZahlenMatrixmitprintf Aufgabe 1.24 Die Klasse PrintWriter Zeigen Sie mit der Klasse ZahlenMatrixmitPrintWriter, dass anstelle der PrintStream-Instanz aus der Aufgabe 1.23 eine PrintWriter-Instanz genutzt werden kann, um daran die Methode printf() aufzurufen. Eine PrintWriter-Instanz kann mithilfe der System.out-Instanz durch Kettung mit einer OutputStreamWriter-Instanz erzeugt werden. In diesem Fall kann jedoch auf die Brückenklasse verzichtet werden, weil die Umwandlung intern im Konstruktor der Klasse PrintWriter automatisch vorgenommen wird. 58 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.10 Standard Ein-/Ausgabe-Kanäle Vergessen Sie nicht, dass ein PrintWriter-Stream im Gegensatz zu einem Stream vom Typ der Klasse PrintStream immer geschlossen werden muss, damit eine Ausgabe erfolgen kann. Java-Dateien: ZahlenMatrixmitPrintWriter.java Programmaufruf: java ZahlenMatrixmitPrintWriter Aufgabe 1.25 Die Klasse PrintWriter als Filterklasse einsetzen Wie in der Aufgabe 1.24 gezeigt wurde, kann die Klasse PrintWriter eingesetzt werden, um formatierte Daten in einen Output-Stream zu schreiben. Dabei wird die PrintWriter-Instanz benutzt, um den Ausgabe-Stream, der auch ein FileStream sein kann, wie eine Filterklasse zu umwickeln, obwohl diese keine Unterklasse von FilterWriter ist, sondern direkt von Writer abgeleitet wird. Dann kann in die Datei mit den Methoden beider Streams geschrieben werden. Zeigen Sie mit der Klasse FileWriterundPrintWriter, worin der Unterschied besteht, wenn in eine Datei mit einem FileWriter-Stream bzw. einem PrintWriter-Stream geschrieben wird, um klarzustellen, warum und wann solche Kettungen einzusetzen sind. Zu diesem Zweck soll ein zeichenorientierter Stream der Klasse FileWriter mit der Datei namens »Printdatei« verbunden werden. Ketten Sie den FileWriterStream mit einem PrintWriter-Stream und schreiben Sie mit den Methoden von beiden Streams die in einem char-Array hinterlegten Zeichen und verschiedene einzelne Zeichen, darunter auch das Zeichen für eine neue Zeile (’\n’), in die Datei. Lesen Sie die Daten über einen FileReader-Stream in ein weiteres Array ein und zeigen Sie diese auf zwei Arten am Bildschirm an: indem Sie ein PrintWriterObjekt erzeugen, mit einer OutputStreamWriter-Instanz für die Ausgabe zwecks Konvertierung ketten und daran die printf()-Methode aufrufen bzw. die printf()-Methode an der System.out-Instanz aufrufen. Java-Dateien: FileWriterundPrintWriter.java Programmaufruf: java FileWriterundPrintWriter Aufgabe 1.26 Die Klasse StringWriter Die Klasse StringWriter definiert einen Character-Stream, der ebenso wie die Instanzen der Klasse CharArrayWriter einen Ausgabe-Stream für einen String bereitstellt. Eine Instanz der Klasse StringWriter schreibt ihre Ausgaben in einen String-Puffer. Mit ihrer Methode getBuffer() wird dieser als Instanz der Klasse 59 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams StringBuffer returniert und mit der Methode toString() als Instanz der Klasse String. Definieren Sie eine Klasse PrintWriterundStringWriter, die in geschachtelten try/catch-Blöcken Ausnahmen vom Typ MissingFormatArgumentException, IllegalFormatConversionException und UnsupportedEncodingException abfängt und bearbeitet. Erfassen Sie mehrere fehlerhafte Statements im Programm, die solche Ausnahmen auslösen. Dabei soll erstens der StackTrace in jedem catch-Block über die Methode printStackTrace() ausgegeben werden, welche die Java-Standard-Klasse Exception von ihrer Oberklasse Throwable erbt. Zweitens soll der StackTrace einer Ausnahme in einen StringWriter-Stream mithilfe eines PrintWriter-Streams geschrieben und mit dessen Methode toString() in einen String für die Weiterverarbeitung umgesetzt werden. Die Methode traceStack() der Klasse PrintWriterundStringWriter, welche diese Aufgabe übernimmt, soll aus dem so erzeugten String den Programmfehler und Dateinamen mit Zeilennummer mithilfe von Methoden der Klasse String ermitteln und diese als zusätzliche Fehlermeldung am Bildschirm anzeigen. Hinweise für die Programmierung: Die printStackTrace()-Methode von Throwable (die Oberklasse aller Exception- und Error-Klassen in Java) schreibt die Trace-Ausgaben von Ausnahmen und Fehlern in einen PrintStream. Daraus ist ersichtlich, in welcher Klasse, welcher Methode und bei welcher Zeilennummer die Ausnahme ausgelöst wurde sowie welche Ausnahme oder welcher Fehler dazu geführt hat. Java-Dateien: PrintWriterundStringWriter.java Programmaufruf: java PrintWriterundStringWriter Aufgabe 1.27 Die System.in-Instanz der Klasse InputStream Definieren Sie eine Klasse UngepufferteTastaturEingaben1 zum Lesen von Tastatureingaben mithilfe der System.in-Instanz und ihrer read()-Methode zum Einlesen von einzelnen Bytes. Das in der Java-Standard-Klasse System definierte Klassenfeld System.in referenziert eine Instanz der Klasse InputStream. Der Benutzer soll aufgefordert werden, über die Bildschirmausgabe »Beliebige Zeichen ueber die Tastatur eingeben und die Enter-Taste druecken«, Eingaben über die Tastatur zu machen. Zeigen Sie analog zur Klasse ByteFileStreams aus der Aufgabe 1.5 die eingelesenen Daten und ihren 7-Bit ASCII-Key-Code am Bildschirm an. 60 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.10 Standard Ein-/Ausgabe-Kanäle Hinweise für die Programmierung: Die parameterlose Methode read() der Klasse InputStream returniert eine intZahl. Diese Zahl ist ein 32-Bit breiter int-Wert, in den der 7-Bit ASCII-Key-Code von dieser Methode konvertiert wurde, falls die Tastatur als Standard-Eingabe diente, bzw. ein 8-Bit langes Byte, falls die Eingabe in eine Datei umgelenkt wurde. Um die Eingabe abzuschließen, muss (Enter) gedrückt werden. Dabei setzt Windows den »carriage return« Zeichen-Code (in ASCII die 13), was sich über die Anzeige der Daten am Bildschirm nachvollziehen lässt. Java-Dateien: UngepufferteTastaturEingaben1.java Programmaufruf: java UngepufferteTastaturEingaben1 Aufgabe 1.28 Das Umlenken des Standard-Eingabe-Kanals In Java wird dem Programmierer nicht die Möglichkeit gegeben, herauszufinden, ob die mithilfe von System.in.read() eingelesenen Zeichen vom StandardInput-Kanal, von der Tastatur oder aus einer Datei eingelesen wurden. Mit der Methode setIn() der Java-Standard-Klasse System kann jedoch festgelegt werden, dass der Standard-Eingabe-Kanal einem bestimmten Input-Stream zugeordnet wird. In die Datei »Eingabedatei« wird mit einem Editor ein beliebiger Text geschrieben. Ändern Sie die Klasse UngepufferteTastaturEingaben1 aus der Aufgabe 1.27 in eine Klasse StandardEingabeKanalUmlenken so ab, dass mit den Anweisungen FileInputStream byteFileIn = new FileInputStream ("Eingabedatei"); und System.setIn (byteFileIn); der Standard-Eingabe-Kanal so gesetzt wird, dass Eingaben nur aus dieser Datei empfangen werden können. Java-Dateien: StandardEingabeKanalUmlenken.java Programmaufruf: java StandardEingabeKanalUmlenken Aufgabe 1.29 Das Einlesen von Tastatureingaben in ein Array Durch den Aufruf der Methode read(byte[] array) an einer Instanz der Klasse InputStream können die Eingaben von der Tastatur in ein byte-Array eingelesen werden. Definieren Sie die Klasse UngepufferteTastaturEingaben2 zum Lesen von Tastatureingaben mithilfe dieser Methode. 61 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Über den Konstruktor der Klasse String, der eine Arrayreferenz als Parameter definiert, können die Bytes des Arrays in ein String-Objekt überführt werden. Übergeben Sie dessen Referenz im Aufruf der Methode System.out.println() für die Anzeige der Daten am Bildschirm. Java-Dateien: UngepufferteTastaturEingaben2.java Programmaufruf: java UngepufferteTastaturEingaben2 Aufgabe 1.30 Das Puffern von Tastatureingaben beim Einlesen Mit der Klasse GepufferteTastaturEingaben soll die Möglichkeit der Kettung des System.in-InputStreams mit einem Stream der Klasse BufferedReader demonstriert werden. Die Klasse BufferedReader definiert die Methode readLine(), die einen String mit dem Inhalt der gepufferten Daten zurückgibt und eine weitere Umsetzung überflüssig macht. Auch in diesem Fall muss die System.in-Instanz zwecks Datenkonvertierung über eine Instanz vom Typ der Brückenklasse InputStreamReader im Konstruktor der Klasse BufferedReader übergeben werden. Die Aufforderung zur Tastatureingabe und die Anzeige der eingelesenen Daten am Bildschirm soll wie auch in den vorangegangenen Aufgaben über den Aufruf der Methode System.out.println() erfolgen. Java-Dateien: GepufferteTastaturEingaben.java Programmaufruf: java GepufferteTastaturEingaben Aufgabe 1.31 Das Schreiben auf den Fehlerkanal mit der System.err-Instanz Mit der Klasse StandardFehlerKanalUmlenken sollen über die Tastatur eingegebene Zeichen über einen ByteArrayOutputStream in eine Datei mit dem Namen »Ausgabedatei« geschrieben werden. Erzeugen Sie einen Ausgabe-Stream vom Typ ByteArrayOutputStream, um die vom Standard-Eingabe-Kanal eingelesenen Zeichen reinzuschreiben. Wie schon mit der Klasse ByteArrayStreams aus der Aufgabe 1.7 gezeigt wurde, kann ein ByteArrayOutputStream mit seiner Methode toString() für eine Bildschirmanzeige in einen String konvertiert und mit der Methode writeTo() in einen anderen Stream kopiert werden. Um Ausgaben auf den Fehlerkanal zu testen, soll der Dateiname »Ausgabedatei« im Konstruktoraufruf eines FileInputStreams übergeben werden, bevor diese Datei 62 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.10 Standard Ein-/Ausgabe-Kanäle angelegt wird. Damit soll gezeigt werden, dass dabei eine Ausnahme vom Typ FileNotFoundException ausgelöst wird und diese mithilfe der Methode System.err.println() auf den Fehlerkanal, der unter Windows auch der Konsole zugeordnet ist, geschrieben werden kann. Diese Ausnahme wird ausgelöst, wenn es sich um ein Dateiverzeichnis und keine gültige Datei handelt, diese nicht existiert oder zum Lesen nicht geöffnet werden kann. Danach soll der Standard-ErrorKanal in eine Datei »StdErrDatei« umgelenkt werden und eine weitere Ausnahmesituation hervorgerufen werden (z.B. eine NullPointerException), um das Schreiben der Fehlermeldung in diese Datei zu prüfen. Beim Erzeugen eines Streams vom Typ der Klasse FileOutputStream wird eine noch nicht vorhandene Datei angelegt. Die vom Konstruktoraufruf geworfene Ausnahme vom Typ FileNotFoundException muss aber auch abgefangen oder weitergereicht werden. Die Ausnahme wird in diesem Fall ausgelöst, wenn es sich um ein Dateiverzeichnis und keine gültige Datei handelt, diese nicht existiert und nicht erzeugt werden kann oder aus irgendeinem Grund nicht geöffnet werden kann. Übergeben Sie im Konstruktoraufruf der Klasse FileOutputStream den Namen »Ausgabedatei« und schreiben Sie den Inhalt des ByteArrayOutputStream in den damit erzeugten Stream. Sehen Sie sich den Inhalt der Dateien »Ausgabedatei« und »StdErrDatei« mit dem Befehl type auf Kommandozeilenebene an. Hinweise für die Programmierung: Wie bereits erwähnt, kann unter Windows der Standard-Fehlerkanal nicht auf Kommandozeilenebene in eine Datei umgelenkt werden. Dies kann jedoch mithilfe der Methode setErr() der Java-Standard-Klasse System erreicht werden, der als Argument ein Output-Stream übergeben werden kann, der mit einer Datei verbunden ist. Java-Dateien: StandardFehlerKanalUmlenken.java Programmaufruf: java StandardFehlerKanalUmlenken Aufgabe 1.32 Einlesen von primitiven Datentypen mithilfe der Klasse Scanner Wie schon erwähnt, kann für das Einlesen von primitiven Datentypen, die in einem Zeichenformat vorliegen, die Klasse Scanner benutzt werden. In der Klasse ScannerfuerStringsundStreams soll ein zeichenorientierter Stream vom Typ der Klasse FileWriter mit der Datei »Scannerdatei« verbunden werden. Der FileWriter-Stream soll mit einem PrintWriter-Stream gekettet werden, um die vom Programm definierten Strings in die Datei als einzelne Zeilen zu schreiben. 63 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Diese Strings sollen ganze Zahlen, Gleitkommazahlen und Buchstaben – durch Leerzeichen getrennt – enthalten wie z. B. zeile1 = "1 2 3 4 5", zeile2 = "1,2 34,5 678,910 745,11 23,45" und zeile3 = "A B C D E". Die Datei »Scannerdatei« soll mithilfe der Methode nextLine() der ScannerKlasse zeilenweise gelesen werden. Übergeben Sie dazu beim Instantiieren im Konstrukor der Klasse Scanner eine FileReader-Instanz mit dem Dateinamen. Die gelesenen Zeilen werden in einen String gespeichert, der zwecks Zeilenzerlegung im Konstruktor einer weiteren Scanner-Instanz übergeben wird. Rufen Sie an dieser Instanz die Methoden der Klasse Scanner für das Überprüfen und Einlesen von primitiven Datentypen und Strings wie hasNextInt(), nextInt(), hasNextDouble(), nextDouble(), hasNext() und next()auf. Java-Dateien: ScannerfuerStringsundStreams.java Programmaufruf: java ScannerfuerStringsundStreams Aufgabe 1.33 Einlesen von Tastatureingaben mithilfe der Klasse Scanner Die Vorgehensweise aus der Aufgabe 1.32 kann auch das Einlesen von Tastatureingaben wesentlich erleichtern. Definieren Sie zur Demonstration eine Klasse GescannteTastaturEingaben, in der Sie die Methode nextLine() der Klasse Scanner aufrufen, um Tastatureingaben, die mit der Enter-Taste abgeschlossen werden, zeilenweise entgegenzunehmen. Dazu soll die System.in-Instanz im Konstruktor der Scanner-Klasse über ihre Referenz übergeben werden. Der Benutzer soll über die Bildschirmausgabe »Zeichenfolgen ueber die Tastatur eingeben und die Enter-Taste druecken« aufgefordert werden, Eingaben über die Tastatur zu machen. Zeigen Sie die so eingelesenen Daten mit der Methode System.out.println() am Bildschirm an. Hinweise für die Programmierung: Mithilfe der Methode hasNextLine() der Klasse Scanner kann überprüft werden, ob eine weitere Zeile beim Einlesen zur Verfügung steht. Diese Methode blockiert jedoch die Eingabe. Definieren Sie, um dies zu verhindern, eine while-Schleife, die bei der Eingabe der Zeichenkette »Ende« verlassen wird. Java-Dateien: GescannteTastaturEingaben.java Programmaufruf: java GescannteTastaturEingaben 64 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.11 ObjectStreams und die Serialisierung von Objekten Aufgabe 1.34 Die printf()-Methode der Klasse Console Definieren Sie eine Klasse AusgabenmitderKlasseConsole, die eine Instanz der Klasse Console erzeugt und an dieser ihre printf()-Methode aufruft, um den String »äöüÄÖÜß« am Bildschirm anzuzeigen. Zeigen Sie den gleichen String auch über den Aufruf der printf()-Methode an einer System.out-Instanz an. Java-Dateien: AusgabenmitderKlasseConsole.java Programmaufruf: java AusgabenmitderKlasseConsole Aufgabe 1.35 Die readLine()-Methode der Klasse Console Definieren Sie eine Klasse EingabenmitderKlasseConsole, die eine Instanz der Klasse Console erzeugt und an dieser ihre readLine()-Methode aufruft, um mit der Tastatur eingegebene Zeichen zu lesen. Eine BufferedReader-Instanz soll mithilfe der Brückenklasse InputStreamReader mit der System.in-Instanz gekettet werden, um an dieser die readLine()Methode der Klasse BufferedReader aufzurufen. Zeigen Sie die eingelesenen Strings in beiden Fällen über den Aufruf der printf()-Methode an der Console-Instanz und der System.out-Instanz am Bild- schirm an. Hinweise für die Programmierung: Achten Sie darauf, dass die Methode readLine() der Klasse Console kein Zeilenende-Zeichen setzt und die Methode readLine() der Klasse BufferedReader eine Ausnahme vom Typ IOException wirft. Java-Dateien: EingabenmitderKlasseConsole.java Programmaufruf: java EingabenmitderKlasseConsole 1.11 ObjectStreams und die Serialisierung von Objekten Der Vorgang, durch den über das Speichern und Zurückgewinnen von Objektzuständen (damit sind wie immer die Werte ihrer Instanzfelder gemeint), Objekte zu einem späteren Zeitpunkt wieder rekonstruiert werden können, wird in der JavaLiteratur als Serialisierung von Objekten bezeichnet. Dazu werden die Java-Standard-Klassen ObjectOutputStream und ObjectInputStream benutzt. Mit der Klasse ObjectOutputStream wird der Inhalt von 65 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Objekten in einen byteorientierten Stream konvertiert, um z.B. in eine Datei geschrieben oder über das Netz übertragen zu werden. Dieser Vorgang wird als Serialisierung bezeichnet. Es können nur Objekte von Klassen serialisiert werden, die das Interface Serializable implementieren. Dies ist ein Marker-Interface, d.h. es enthält weder Konstantendefinitionen noch Methodenspezifikationen und dient lediglich dazu, Klassen, die eine Serialisierung unterstützen, zu kennzeichnen. Bei einer Serialisierung werden die Werte der Instanzfelder von Objekten in der über den Methodenaufruf writeObject() übergebenen ObjectOutputStreamInstanz gespeichert. Gleichzeitig werden der Klassenname mit der Klassensignatur, die Feldnamen und der Typ von Feldern darin festgehalten. Im Fall von Referenzfeldern werden die entsprechenden Daten für alle damit referenzierten Instanzen rekursiv ermittelt und gespeichert. Mit der Klasse ObjectInputStream und deren Methode readObject() wird die Deserialisierung von Objekten realisiert. Weil beim Serialisieren die Methoden von Klassen nicht gespeichert werden, muss die .class-Datei vorhanden sein, um mit deren Hilfe und den gespeicherten Daten ein Objekt vollständig rekonstruieren zu können. Die Methode readObject() returniert eine Referenz auf das wiederhergestellte Objekt. Die Instanzfelder einer Klasse, deren Werte nicht gespeichert werden sollen (wie z.B. Passwortfelder), müssen mit dem Modifikator transient deklariert werden. Klassenfelder (mit dem Modifikator static in einer Klasse definiert) werden auch nicht gespeichert, da diese nicht zu den Instanzen einer Klasse gehören. Die Klassen ObjectOutputStream und ObjectInputStream implementieren die Interfaces DataOutput und DataInput. D.h., sie implementieren auch deren Methoden, die zum Schreiben und Lesen von primitiven Datentypen und Strings in (aus) diese (diesen) Streams benutzt werden können. 1.12 Die Versionsverwaltung von Klassen Beim Serialisieren von Objekten wird eine Versionsnummer (SerialVersionUID) vergeben, die zur Identifikation dient. Diese wird als long-Wert definiert und enthält eine Verschlüsselung von Daten aus der Klasse, zu der das serialisierte Objekt gehört. Sie kann mit dem Java-Tool serialver gefolgt vom Namen der Klasse auf Kommandozeilenebene angezeigt werden. Die Versionsnummer einer Klasse wird abgeändert, wenn in der Klassendefinition Änderungen erfolgen, auch wenn eine Methode abgeändert oder neu hinzugefügt wird und die Werte von Feldern gleich bleiben. Es sollen ja schließlich beim Deserialisieren von Objekten der Klasse keine falschen Daten zurückgeliefert werden. So wird beim Deserialisieren die Versionsnummer des serialisierten Objekts mit der aktuellen Versionsnummer der Klasse verglichen und bei ungleichen Werten eine Ausnahme vom Typ InvalidClassException ausgelöst. 66 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.12 Die Versionsverwaltung von Klassen Auf Programmebene kann die Versionsnummer mithilfe von Methoden der Klasse ObjectStreamClass abgefragt werden. Diese Klasse ist von der Object-Klasse abgeleitet und implementiert auch die Serializable-Schnittstelle. Ihre Methode lookup(), die als Parameter eine Referenz vom Typ der parametrisierten Klasse Class<T> definiert, liefert eine Referenz vom Typ der eigenen Klasse zurück, an der die Methode getSerialVersionUID() aufgerufen werden kann. Aufgabe 1.36 Das Speichern und Einlesen eines Objektes Definieren Sie eine Klasse Punkt mit zwei Instanzfeldern vom Typ double, welche die Koordinaten x und y eines Punktes im zweidimensionalen kartesischen Koordinatensystem beschreiben. Die Klasse definiert die Zugriffsmethoden setX(), setY(), getX() und getY() für das Schreiben und Lesen der Koordinatenwerte und eine Methode anzeige() für eine Punktanzeige am Bildschirm in der Form (x, y). In einer weiteren Klasse mit dem Namen ObjectStreams sollen Objekte vom Typ der Klasse Punkt in einer Datei so abgespeichert werden, dass sie aus dieser Datei in ihrem ursprünglichen Zustand wieder zurückgewonnen werden können. Die Klasse Punkt, deren Objekte serialisiert werden sollen, muss, wie bereits erwähnt, die Schnittstelle Serializable implementieren. Die Klasse ObjectStreams definiert zwei globale Referenzen vom Typ der Klasse Punkt, originalPunkt und serialPunkt. Für das Serialisieren eines Objektes wird ein byteorientierter FileOutputStream mit einem ObjectOutputStream gekettet und an diesem die Methode writeObject() aufgerufen, in der eine PunktInstanz über die originalPunkt-Referenz übergeben wird. Ein FileInputStream soll mit der vorher erzeugten Datei verbunden und mit einem ObjectInputStream gekettet werden. Die an diesem Stream aufgerufenen Methode readObject() returniert mit serialPunkt eine Referenz auf das wiederhergestellte Objekt. Am originalen und deserialisierten Objekt soll die Methode anzeige() der Klasse Punkt aufgerufen werden, um die Feldinhalte der Instanzen zu vergleichen. Java-Dateien: Punkt.java, ObjektStreams.java Programmaufruf: java ObjektStreams Aufgabe 1.37 Das Speichern und Einlesen von Objekten, deren Klassen Referenzfelder definieren Die Klasse Kreis definiert die globalen Referenzen p und s vom Typ der Klassen Punkt und String, die Instanzfelder r vom Typ double und instanzZaehler 67 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams vom Typ int sowie das Klassenfeld klassenZaehler vom Typ int. Sie soll demonstrieren, dass im Fall von Referenzfeldern die entsprechenden Daten für alle damit referenzierten Instanzen auch abgespeichert und zurückgewonnen werden können. Das Instanzfeld instanzZaehler, das wie auch das Klassenfeld klassenZaehler im Konstruktor der Klasse inkrementiert wird, soll nicht mit serialisiert werden und muss deswegen mit dem Modifikator transient deklariert werden. Definieren Sie Zugriffsmethoden für alle Instanzfelder der Klasse und überschreiben Sie die toString()-Methode der Klasse Object, in der Sie den im Konstruktor übergebenen String anzeigen, die Methode anzeige() der Klasse Punkt aufrufen und die Gleichung des Kreises mit dem im Konstruktor übergebenen Wert für Radius und Mittelpunkt returnieren. Zum Testen der Serialisierung soll die Klasse ErweiterteSerialisierung erstellt werden, die eine Instanz der Klasse Kreis erzeugt und auf die gleiche Art und Weise wie in der Aufgabe 1.36 diese serialisiert und deserialisiert. Am originalen und deserialisierten Objekt sollen die Methoden der Klasse Kreis aufgerufen werden. Java-Dateien: Punkt.java, Kreis.java, ErweiterteSerialisierung.java Programmaufruf: java ErweiterteSerialisierung Aufgabe 1.38 Die SerialVersionUID einer Klasse Erstellen Sie eine Klasse Schule, welche mit dem Schlüsselwort enum die Enumerationen mit den Namen SchuelerListe, NotenListe und FachListe, mit beliebigen Namen für Schüler, Noten und Fachangaben, definiert. Die Enumeration NotenListe soll einen Konstruktor mit einem int-Parameter und die Zugriffsmethoden setNote() und getNote() definieren, die es ermöglichen, den Aufzählungskonstanten unterschiedliche Werte zuzuweisen bzw. deren Werte zu lesen. Eine weitere Klasse SchulemitVersionUID definiert die Instanzfelder vom Typ String name und fach und ein Instanzfeld vom Typ int note. Ein weiteres Instanzfeld testVersionUID vom Typ String soll zum Testen der Vergabe einer Versionsnummer beim Serialisieren dienen. Anhand der Methode pruefen() dieser Klasse soll abgefragt werden, ob die im Konstruktor übergebenen Werte für Name, Fach und Note unter den Aufzählungskonstanten der Enumerationen zu finden sind und entsprechende Meldungen am Bildschirm angezeigt werden. Mithilfe der Klasse SerialisierungTest sollen zwei Objekte vom Typ der Klasse SchulemitVersionUID serialisiert und deserialisiert werden. Definieren Sie zu diesem Zweck zwei eigene Methoden writeObject() und readObject(), in 68 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.13 Die Klasse RandomAccessFile denen Sie die dazu erforderlichen Statements aus den vorangegangenen Aufgaben entsprechend zusammenfassen, und rufen Sie diese in der main()-Methode der Klasse auf. Rufen Sie an den so erzeugten Instanzen die Methode pruefen() auf und zum Anzeigen der SerialVersionUID die Methoden lookup() und getSerialVersionUID() der Klasse ObjectStreamClass. Ändern Sie den Feldtyp von testVersionUID auf int ab und entwerten Sie den Methodenaufruf für Ihre writeObject()-Methode durch das Setzen von Kommentarzeichen. Dadurch wird verhindert, dass die Objekte erneut serialisiert werden. Mit dem Aufruf der Methode readObject() wird versucht, die im vorhinein schon gespeicherten Werte zu deserialisieren. Beim Lesen der Werte wird die vorher gespeicherte SerialVersionUID der Klasse mit der nach dem erneuten Übersetzen neu vergebenen verglichen und eine Ausnahme vom Typ InvalidClassException ausgelöst. Hinweise für die Programmierung: Für den Vergleich mit den im Konstruktoraufruf übergebenen Werten können die Aufzählungskonstanten mithilfe der Methode values() der generischen Klasse EnumMap<K,V> über eine erweiterte for-Schleife aus den Enumerationen gelesen werden. Die serialVersionUID kann auch vom Programmierer selbst in der Klassendefinition als long-Wert definiert werden. Dieser Wert wird vom System berücksichtigt und sollte bei einer Änderung der Klassendefinition immer mit abgeändert werden, siehe dazu die im Lösungsvorschlag auskommentierte Konstantendefinition. Java-Dateien: Schule.java, SchulemitVersionUID.java, SerialisierungTest.java Programmaufruf: java SerialisierungTest 1.13 Die Klasse RandomAccessFile In und aus Dateien kann nicht nur mithilfe von Streams geschrieben bzw. gelesen werden, wobei die Bearbeitung der Daten sequentiell erfolgt, sondern auch über einen sogenannten »wahlfreien Zugriff«. Die Klasse RandomAccessFile, über welche dieser Zugriff ermöglicht wird, implementiert die Schnittstellen DataOutput und DataInput und ihre Instanzen verhalten sich ähnlich wie große Arrays, die in einem Dateisystem gespeichert sind. Im Gegensatz zu Streams ist dabei ein abwechselndes Lesen und Schreiben von Daten erlaubt. Die Klasse besitzt zwei Konstruktoren, in denen über eine File- bzw. String-Referenz eine Datei bzw. deren Name übergeben werden kann. Beide Konstruktoren definieren einen zweiten Parameter vom Typ String, der den Eröffnungsmodus der angegebenen Datei definiert. Ist dieser »r«, wird die Datei zum Lesen, mit »rw« zum Lesen und Schreiben und mit »rws« und »rwd« zu einem gesicherten Lesen und Schreiben geöffnet. 69 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Die Schreib- und Leseposition wird über einen Dateizeiger (»file pointer«) verwaltet. Dadurch, dass die Klasse die write()- und read()-Methoden des DataOutput- und DataInput-Interface implementiert, können damit wie auch im Fall der Klassen DataOutputStream und DataInputStream sowohl primitive Datentypen und Strings als auch Bytes manipuliert werden. Der Dateizeiger steht nach dem Öffnen einer Datei immer am Dateianfang und wird von den write()- und read()-Methoden immer eine Position weiter gesetzt. Die Methode getFilePointer() ermittelt die aktuelle Position des Dateizeigers, indem sie die Anzahl der Bytes vom Dateianfang bis zur Zeigerposition zurückliefert. Die Methoden seek() und skipBytes() ermöglichen ein gezieltes Setzen des Dateizeigers. Während die Methode seek() den Dateizeiger absolut, d.h. ausgehend vom Beginn der Datei positioniert, dient die Methode skipBytes() einer relativen Positionierung, die von der gerade aktuellen Position des Zeigers ausgeht. Beim Schreiben über das Dateiende hinaus wird eine Datei mit wahlfreiem Zugriff automatisch erweitert. Die Länge der Datei kann mithilfe der Methode length() ermittelt werden und entspricht der Anzahl der in der Datei gespeicherten Bytes, die von der Anzahl der gespeicherten Zeichen abweichen kann. Damit kann beim Lesen das Dateiende überprüft werden. Wird versucht, darüber hinaus zu lesen, wird von allen readxxx-Methoden für primitive Datentypen und der readUTF()Methode für das Lesen von Strings sowie auch von den readFully()-Methoden eine Ausnahme vom Typ EOFException ausgelöst. Die readLine()-Methode liefert den Wert null zurück und die read()-Methoden für Bytes den Wert -1. Aufgabe 1.39 Wahlfreier Zugriff auf eine Datei Definieren Sie eine Klasse RandomAccessDatei, die in ihrer main()-Methode eine Instanz vom Typ RandomAccessFile über einen der Konstruktoren dieser Klasse erzeugt. In die so bereitgestellte Datei sollen mehrere Textzeilen mit den Inhalten: »ByteZeile1«, »CharZeile1«, »UTFZeile1«, »ByteZeile2«, »CharZeile2«, »UTFZeile2« etc. mithilfe der Methoden writeBytes(), writeChars() und writeUTF() der Klasse RandomAccessFile geschrieben werden. Nutzen Sie das Newline-Zeichen (‘\n’), um ein Zeilenende zu markieren. Ermitteln Sie die Größe der Datei und die Zeigerposition nach der Durchführung aller Schreiboperationen und zeigen Sie diese Werte am Bildschirm an. Danach soll der Dateizeiger an den Dateianfang für das Lesen der Dateieinträge positioniert werden. Definieren Sie eine Methode lesenDatei(), in der Sie mithilfe der Methoden readLine() und readUTF() die Textzeilen nacheinander lesen und danach ausgeben. 70 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.14 Erweiterung der I/O-Funktionalität mit Java 7 Anstelle des Textes »ByteZeile« soll in jede der Textzeilen über absolute oder relative Zeigerpositionierung der Text »NeueZeile« geschrieben werden. Benutzen Sie dazu die Methoden seek() bzw. skipBytes() und rufen Sie erneut die Methode leseDatei() auf, um die durchgeführten Änderungen zu prüfen. Java-Dateien: RandomAccessDatei.java Programmaufruf: java RandomAccessDatei 1.14 Erweiterung der I/O-Funktionalität mit Java 7 Die mit NIO.2 bezeichneten I/O-Erweiterungen von Java 7 wurden dem Package java.nio von Java 1.4 hinzugefügt und beinhalten die zwei größeren Themen: 쐽 Das neue File-System-API 쐽 Asynchrone I/O-Erweiterungen Auf ein Dateisystem kann in Java 7 mit Methoden der Klassen FileSytem, FileSystems und FileStore aus dem Paket java.nio.file zugegriffen werden. Die zwei wichtigsten Methoden, die der Konstruktion von Dateisystemen dienen, sind die Factory-Methoden getDefault() und getFileSystem(URI uri). Die Methode getDefault() gibt die FileSystem-Instanz zurück, auf der ein Programm gerade ausgeführt wird. Zwei weitere Implementierungen des JDK, das Interface Path und die Klasse Files aus dem gleichen Paket, ergänzen das File-System-API, indem sie die Funktionalität der Klasse File komplett ersetzen und gleichzeitig erweitern. Die FileKlasse selbst bleibt auch bestehen (wurde nicht als deprecated gekennzeichnet) und Java 7 bietet mit der Methode toPath() die Möglichkeit, ein File-Objekt in ein neues Path-Objekt umzusetzen. Laut Hinweisen aus der Java-Literatur sollte von alten und neuen Klassen parallel Gebrauch gemacht werden, um die I/O-Funktionalität von Java sinnvoll nutzen zu können. An einer mit getDefault() ermittelten FileSytem-Instanz kann die Methode getPath() aufgerufen werden, um den Pfadnamen für eine/ein im Aufruf übergebene Datei oder Dateiverzeichnis zu ermitteln. Die einfachste Möglichkeit, eine Path-Referenz zu ermitteln, besteht darin, eine der get()-Methoden der Klasse Paths aufzurufen. Damit können absolute und relative Pfadnamen bestimmt oder auch Pfade über Shortcuts mit den Bezeichnungen ».« (für das aktuelle Directory) bzw. »..« (für ein übergeordnetes Directory) definiert werden. Die Klasse Files stellt Klassenmethoden zum Ausführen von Operationen auf Dateien und Dateiverzeichnissen zur Verfügung wie z.B. delete(), move() und copy(), die im Fehlerfall eine Exception werfen. So wird das API-Design dahin gehend verbessert, dass vom Programm aus Fehler näher beschrieben werden können und darauf reagiert werden kann. Weitere Methoden der Klasse Files erlau- 71 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams ben den Zugriff auf Dateiattribute, die vom NIO.2 in Views gruppiert werden, weil die unterschiedlichen Dateisysteme verschiedene Bezeichnungen nutzen, um Attribute zu definieren. Die neuen Methoden ermöglichen zusätzlich zu den gleichartigen Methoden der File-Klasse so genannte Bulk-Zugriffe, mit denen mehrere Attribute gleichzeitig gelesen oder geschrieben werden können. In vielen der Beispiele aus den vorangegangenen Aufgaben konnten wir sehen, wie Streams für I/O-Operationen eingesetzt werden, um Daten in ein Java-Programm von einer Informationsquelle einzulesen und zu einem Ziel zu senden. Weil in Stream-orientierten I/O-Systemen ein Java-Programm immer nur ein Byte von einem InputStream lesen bzw. in einen OutputStream schreiben kann, leidet die Performance von Lese/Schreibzugriffen. Mit Java 1.4 wurde mit dem NIO (»new imput/output«) ein neues I/O-API eingeführt, das auf Channels und Buffers basiert. Channels sind Streams ähnlich, unterscheiden sich jedoch von diesen in drei wichtigen Merkmalen: 쐽 Während Streams entweder zum Lesen oder zum Schreiben geöffnet werden können, unterstützen Channels Reads und Writes gleichzeitig. 쐽 Channels können nur aus einem Puffer lesen bzw. in einen Puffer schreiben. 쐽 Channels können asynchron gelesen und geschrieben werden. Ein Channel ist eine offene Verbindung zwischen einem Java-Programm und einer Quelle bzw. einem Ziel, die/das benutzt wird, um Daten zu lesen oder zu schreiben. Die Implementierung von Channels basiert auf dem Channel-Interface aus dem Paket java.nio.channels. Ein ReadableByteChannel-Objekt kann benutzt werden, um Daten aus einer Quelle in einen ByteBuffer zu lesen und ein WritableByteChannel-Objekt, um Daten aus einem ByteBuffer in ein Ziel zu schreiben. Ohne auf alle Details einzugehen, wollen wir noch erwähnen, dass die Klassen FileInputStream und FileOutputStream für das NIO für die Arbeit mit Channels angepasst wurden. Sie definieren eine Methode getChannel(), die ein FileChannel-Objekt zurückgibt, das genutzt werden kann, um Daten aus/in einer/ eine Datei zu lesen bzw. zu schreiben. Die Klasse FileChannel wurde mit Java 7 ebenfalls abgeändert und an das neue File-System-API angepasst. Wird ein FileChannel-Objekt mit Hilfe eines Path-Objekts ermittelt, können zwei neue open()-Methoden zum Öffnen des Channels aufgerufen werden. Die Klasse implementiert mit Java 7 auch das neue SeekableByteChannel-Interface, das für die Nutzung von RandomAccessFiles eingesetzt werden kann. Im Gegensatz zu synchronen I/O-Operationen, bei denen stets auf eine Antwort gewartet wird, geben asynchrone Operationen sofort die Kontrolle an das Java-Programm zurück, ohne auf eine Antwort zu warten. Ansätze für eine asynchrone I/OVerarbeitung gibt es in Java schon seit der Version 1.4. Die mit dem Einsatz verbundenen Schwierigkeiten dienten als Anlass, mit Java 7 drei neue Typen von asynchronen Channels für den Zugriff auf Dateien und in der Client-ServerKommunikation einzuführen: 72 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.14 Erweiterung der I/O-Funktionalität mit Java 7 쐽 AsynchronousFileChannel 쐽 AsynchronousSocketChannel 쐽 AsynchronousServerSocketChannel Während synchrone I/Os in einer Multithread-Umgebung (siehe dazu das nachfolgende Kapitel) die Performance negativ beeinflussen können, unterstützen asynchrone I/Os die Nebenläufigkeit und Skalierbarkeit von Programmen, was im heutigen Zeitalter von Multi-Prozessor- und Multicore-Architekturen immer mehr an Bedeutung gewinnt. Asynchrone Channels unterstützen und überwachen multiple I/O-Operationen (wie Verbindungsaufbau, Lesen und Schreiben), die parallel von mehreren Threads ausgeführt werden. Wie bereits erwähnt, basieren die Methoden zum Lesen und Schreiben aus/in Channels auf so genannte ByteBuffers. Im Gegensatz zu einem synchronen FileChannel verfügen asynchrone FileChannels nicht über eine aktuelle Zeigerposition bzw. einen Offset beim Lesen und Schreiben aus/in Dateien, weshalb bei jedem neuen Vorgang diese Position spezifiziert werden muss. Die Klasse ByteBuffer wird von der abstrakten Oberklasse Buffer abgeleitet, die eine endliche Sequenz von Elementen von einem angegebenen primitiven Typ definiert. Die drei wichtigsten Eigenschaften einer Buffer-Instanz sind: 쐽 Die Größe (»capacity«), die die maximale Anzahl von Elementen, die darin gespeichert werden können, spezifiziert. 쐽 Das Limit (»limit«), das dem Index des ersten Elements, das nicht geschrieben bzw. gelesen werden kann, gleich ist. Mit anderen Worten, wenn aus einem Puffer heraus geschrieben wird, spezifiziert das Limit, wie viele Daten zum Lesen übrig geblieben sind, und wenn daraus gelesen wird, wie viel Platz zum Speichern von Daten noch vorhanden ist. 쐽 Die Position, die den Index des nächsten Elements, das geschrieben bzw. gelesen werden soll, vorgibt. Die flip()-Methode setzt das Limit auf die aktuelle Position und danach die Position auf 0. Die Methode clear() setzt das Limit gleich der Puffergröße und die Position auf 0. Darum sollte, wenn aus einem Puffer nur geschrieben wird, vor dem Schreiben das Limit mit flip() gesetzt werden, um die korrekte Anzahl von Bytes zu übertragen. Die Klasse ByteBuffer verfügt über keinen Konstruktor und definiert mehrere Methoden für das Lesen und Schreiben von Daten aus/in ByteBuffer-Instanzen. Derartige Instanzen werden mit der Methode allocate() der Klasse erzeugt, in deren Aufruf die Größe des Puffers übergeben wird. Wir weisen an dieser Stelle auf die nachfolgenden Aufgaben hin, um die Syntax und Benutzung von neuen Klassen und Methoden einzuüben. Eine Benutzung der Klasse AsynchronousFileChannel in einer Multithreading-Umgebung wird mit 73 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams der Klasse ExecutorsmitAsynchronousFileChannel aus der Aufgabe 2.46 illustriert. Die AsynchronousServerSocketChannel- und AsynchronousSocketChannelKlassen werden in Kapitel 7 näher beschrieben. Aufgabe 1.40 Das Interface Path und die Klasse Files von Java 7 In der Klasse DateiundVerzeichnisVerwaltungmitJava7 soll die Klasse DateiundVerzeichnisVerwaltung aus der Aufgabe 1.1 mit Hilfe des neuen FileSystem-APIs überarbeitet werden. Erzeugen Sie mehrere File-Objekte wie gehabt über die unterschiedlichen Konstruktoren der Klasse File und rufen Sie daran die static-Methode toPath() der Klasse Files auf, um diese in ein Path-Objekt umzusetzen. Die neue Referenz kann im Aufruf der Methoden createFile() bzw. createDirectory() zum Erzeugen von Dateien und Verzeichnissen übergeben werden. Alternativ kann eine Path-Referenz auf eine Datei oder ein Dateiverzeichnis aus dem aktuellen Dateisystem über den geketteten Methodenaufruf FileSystems.getDefault().getPath() ermittelt werden. Beachten Sie dabei, dass ein im Methodenaufruf als String übergebenes Verzeichnis (oder eine Datei) nicht vorhanden sein muss und eine Ausnahme vom Typ FileAlreadyExistsException geworfen wird, wenn Dateien oder Verzeichnisse schon existieren. Weil verschiedene Dateisysteme unterschiedliche Sichten auf die Art und Weise, wie Dateiattribute ausfindig gemacht werden, haben können, gruppiert das NIO.2 die Attribute in Views. Der Zugriff auf eine View kann für die Ermittlung aller Attribute mit der Methode readAttributes() erfolgen oder mit der Methode getAttribute() für einzelne Attribute. Die vom NIO.2 definierten sechs Views können über den Aufruf der Methode supportedFileAttributeViews() ermittelt werden. Richten Sie sich nach dem Lösungsvorschlag zu dieser Aufgabe, um z.B. alle oder einzelne Attribute aus dem BasicFileAttributeView und dem DosFileAttributeView zu bestimmen bzw. eigene Attribute für Verzeichnisse und Dateien im UserDefinedFileAttributeView zu setzen (und ggf. wieder zu löschen). Rufen Sie zum Vergleich Methoden wie z.B. delete() der Klassen File und Files auf und achten Sie darauf, dass die Methoden der Klasse Files eine IOException werfen, die es ermöglicht, auf eventuelle Fehler zu reagieren. Benutzen Sie den mit Java 7 eingeführten Disjunction-Typ für Exceptions, der die Möglichkeit bietet, mehrere Ausnahmen gleichzeitig in einem catch()-Block zu behandeln. Java-Dateien: DateiundVerzeichnisVerwaltungmitJava7.java Programmaufruf: java DateiundVerzeichnisVerwaltungmitJava7 74 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.14 Erweiterung der I/O-Funktionalität mit Java 7 Aufgabe 1.41 Die Klassen FileChannel und AsynchronousFileChannel In Analogie zu der Klasse ByteFileStreams aus der Aufgabe 1.5 sollen zwei neue Klassen SynchroneFileChannels und AsynchroneFileChannels zum Schreiben und Lesen von char-Werten in/aus eine/einer Datei definiert werden. In beiden Klassen wird ein byte-Array mit den Großbuchstaben des Alphabets initialisiert und die Methode ByteBuffer byteBuffer = ByteBuffer.allocate(26) aufgerufen, um einen ByteBuffer zu erzeugen. Schreiben Sie in diesen den Inhalt des Arrays mit der Methode wrap(). In der Klasse SynchroneFileChannels sollen für den Zugriff auf die Datei C:/ EJ_Uebungsbuch3/Datei2, die mit der vorangegangenen Aufgabe angelegt wurde, sowohl die Möglichkeiten von Java 1.4 als auch die von Java 7 zum Ermitteln eines FileChannels genutzt werden: FileChannel fileChannel = new FileOutputStream(file).getChannel() bzw. FileChannel fileChannel = FileChannel.open(path, Enumset.of(StandardOpenOption.READ, StandardOpenOption.WRITE), wobei Path path = Paths.get("C:/EJ_Uebungsbuch3", "Datei2") eine Referenz auf den Pfadnamen der Datei definiert. In beiden Fällen kann der Inhalt eines ByteBuffers mit fileChannel.write(byteBuffer) auf den Channel geschrieben werden und mit fileChannel.read(byteBuffer) aus der Datei über den Channel in einen ByteBuffer gespeichert werden. In der Klasse AsynchroneFileChannels wird für das Schreiben in eine Datei eine AsynchronousFileChannel-Instanz erzeugt und zum Schreiben geöffnet: AsynchronousFileChannel asynchronousFileChannel = AsynchronousFileChannel.open(path,StandardOpenOption.WRITE), wobei die Referenz Path path = Paths.get("C:/EJ_Uebungsbuch2", "Datei5") vorher ermittelt werden muss. Der Inhalt des Puffers soll mit der Methode write(ByteBuffer src, long position) ab der Position 0 auf den Channel geschrieben werden. Die write()-Methode der Klasse AsynchronousFileChannel liefert in einer Future<Integer>-Instanz die Anzahl der geschriebenen Bytes, die mit ihrer get()-Methode ermittelt werden kann. Um das Ergebnis zu prüfen, wird eine AsynchronousFileChannel-Instanz zum Lesen mit der Option StandardOpenOption.READ geöffnet, die Bytesequenz aus dem Channel ab der Position 0 gelesen und in einen weiteren ByteBuffer übertragen. Die read()-Methode liefert ebenfalls in einer Future<Integer>-Instanz die Anzahl der gelesenen Bytes zurück. Dekodieren Sie in beiden Klassen die Inhalte der ByteBuffers für eine Anzeige am Bildschirm wie folgt: Charset.forName(System.getProperty("file.encoding")).decode(byteBuffer)), nachdem Sie im Vorhinein deren Position auf 0 und das Limit gleich der aktuellen Position zurückgestellt haben. 75 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Hinweise für die Programmierung: Das Schreiben und Lesen von Bytes in/aus die/der Datei mit einem AsynchronousFileChannel wird über den Aufruf der Methoden write() und read() angestoßen und danach asynchron ausgeführt. Mit dem Aufruf der Methode isDone() des Future-Interface kann gewartet werden, bis der Vorgang beendet wurde. Diese Methode gibt true zurück, wenn ein Task komplett abgearbeitet wurde. Das generische Interface Future wurde mit Java 5 dem Paket java.util.concurrent hinzugefügt. Eine detaillierte Beschreibung und Anwendung seiner Methoden sowie der Klassen, die das Interface implementieren, finden Sie in Kapitel 2. Weil die Klassen FileChannel und AsynchronousFileChannel das AutoCloseable-Interface implementieren, kann zum Schließen von Channels alternativ zur close()-Methode das neue Feature von Java 7 »try with ressource« benutzt werden, indem der entsprechende Programmcode in einen derartigen try/catchBlock gepackt wird. Java-Dateien: SynchroneFileChannels.java, AsynchroneFileChannels.java Programmaufruf: java AsynchroneFileChannels, java SynchroneFileChannels 1.15 Lösungen Lösung 1.1 Die Klasse DateiundVerzeichnisVerwaltung import java.io.*; import java.net.*; public class DateiundVerzeichnisVerwaltung { public static void main(String argFile[]) { File[] f = new File[11]; try { // File-Objekte über die unterschiedlichen Konstruktoren erzeugen // Im Konstruktor der Klasse File kann ein Pfadname als String// Referenz übergeben werden, welcher in einen abstrakten // Pfadnamen konvertiert wird f[0] = new File("C:/EJ_Uebungsbuch1/"); // Anschließend muss die Methode mkdirs() aufgerufen werden, // welche das Unterverzeichnis erzeugt f[0].mkdirs(); f[1] = new File("C:/EJ_Uebungsbuch2/"); f[1].mkdirs(); f[2] = new File("C:/EJ_Uebungsbuch3/"); f[2].mkdirs(); // Datei oder Verzeichnisname als Argument im Programmaufruf // übergeben f[3] = new File(argFile[0]); // Im Konstruktor der Klasse File wird der Pfadname und der 76 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen // Dateiname als String-Referenz übergeben f[4] = new File("C:/EJ_Uebungsbuch1/", "TestDatei"); // Anschließend muss die Methode createNewFile() aufgerufen // werden, welche die Datei erzeugt f[4].createNewFile(); // Im Konstruktor der Klasse File wird ein abstrakter Pfadname // als File-Referenz übergeben f[5] = new File(f[2], "TestDateiNeu"); f[5].createNewFile(); // Die Syntax von URL- und URI-Schemata anzeigen URL url = new File("C:/EJ_Uebungsbuch2/").toURI(). toURL(); System.out.println(url.toString()); f[6] = new File(url.toURI()); URI uri = new File("C:/EJ_Uebungsbuch3/").toURI(); System.out.println(uri.toString()); // Den Konstruktor mit einer URI-Referenz als Parameter aufrufen // Das über file:URI angegebene URI-Objekt wird in einen // abstrakten Pfadnamen umgesetzt f[7] = new File(uri); URL url1 = new URL("file:/C:/EJ_Uebungsbuch1/TestDatei"); URI uri1 = new URI( "file:/C:/EJ_Uebungsbuch3/TestDateiNeu"); f[8] = new File(url1.toURI()); f[9] = new File(uri1); // Pfadname als String-Referenz übergeben f[10] = new File("C:/EJ_Uebungsbuch2/Datei"); f[10].createNewFile(); // Informationen über die erstellten Dateien und Verzeichnisse // ermitteln und anzeigen for(int i=0; i<11; i++) { if(f[i].exists() && f[i].isFile()) { System.out.println(f[i].getName() + " ist eine Datei"); } else if(f[i].exists() && f[i].isDirectory()) { System.out.println(f[i].getName() + " ist ein Dateiverzeichnis"); } else { // Ist das im Konstruktoraufruf übergebene Argument fehlerhaft? System.out.println("Fehler: " + f[i]); } } // Dateien umbenennen und löschen boolean b = f[10].renameTo(new File( "C:/EJ_Uebungsbuch2/DateiNeu")); 77 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams System.out.println(f[10].getName() + " " + b); f[10].delete(); System.out.println(f[10].getName()); } catch(NullPointerException e) { System.out.println(e.getMessage()); e.printStackTrace(); } catch(ArrayIndexOutOfBoundsException e) { System.out.println("Im Programmaufruf einen Verzeichnis" + " oder Dateinamen angeben"); e.printStackTrace(); } catch(IOException e) { System.out.println(e.getMessage()); e.printStackTrace(); } catch(URISyntaxException e) { System.out.println(e.getMessage()); e.printStackTrace(); } } } Programmausgaben file:/C:/EJ_Uebungsbuch2/ file:/C:/EJ_Uebungsbuch3/ EJ_Uebungsbuch1 ist ein Dateiverzeichnis ... EJ_Uebungsbuch2 ist ein Dateiverzeichnis ... test.ser ist eine Datei TestDatei ist eine Datei ... Datei true Datei file:/C:/EJ_Uebungsbuch2/ file:/C:/EJ_Uebungsbuch3/ EJ_Uebungsbuch1 ist ein Dateiverzeichnis ... DateiundVerzeichnisVerwaltung.java ist eine Datei ... test.ser ist eine Datei TestDatei ist eine Datei ... Datei true Datei 78 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Lösung 1.2 Die Klasse DateiundVerzeichnisListen import java.io.*; import java.net.*; public class DateiundVerzeichnisListen { public static void main(String argFile[]) { try { File v1, v2; File[] f1 = new File[5]; File[] fListe1, fListe2, fRoots; // Das Home-Verzeichnis ermitteln File fHome = new File(System.getProperty("user.home")); // Das Home-Verzeichnis auflisten File[] f2 = fHome.listFiles(); // Eigenes Dateiverzeichnis als File-Instanz erzeugen v1 = new File("C:/EJ_Uebungsbuch/"); // Anschließend muss die Methode mkdirs() aufgerufen werden, // welche dass Unterverzeichnis von C:\ erzeugt v1.mkdirs(); // Unterverzeichnis von C:/EJ_Uebungsbuch/ erzeugen v2 = new File(v1, "Dateien/"); v2.mkdirs(); // Dateien als File-Instanzen im Unterverzeichnis erzeugen for(int i=0; i<f1.length; i++) { f1[i] = new File(v2, "Datei"+i); f1[i].createNewFile(); } // Die Verzeichnisse v1 und v2 auflisten fListe1 = v1.listFiles(); fListe2 = v2.listFiles(); // Verzeichnisse und Dateien anzeigen System.out.println(); System.out.println("Unterverzeichnisse und Dateien " + "von " + v1.getName() + ":"); anzeige(fListe1); anzeige(fListe2); // Leerzeile schreiben System.out.println(); System.out.println("Unterverzeichnisse und Dateien " + "von " + fHome.getName() + ":"); anzeige(f2); // Verfügbare Laufwerke ermitteln und anzeigen fRoots = File.listRoots(); // Leerzeile schreiben System.out.println(); System.out.println("Verfuegbare Laufwerke:"); 79 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams for(int i=0; i<fRoots.length; i++) System.out.print(fRoots[i].getPath() + " " + fRoots[i]. getTotalSpace() + " " + fRoots[i].getFreeSpace() + "; "); } catch(IOException e) { System.out.println(e.getMessage()); e.printStackTrace(); } } // Anzeige von File-Listen public static void anzeige(File[] file) { for(File fileName: file) { if(fileName.isFile()) { System.out.println(fileName.getName() + " ist eine Datei"); } else if(fileName.isDirectory()) { System.out.println(fileName.getName() + " ist ein Dateiverzeichnis"); } } } } Programmausgaben Unterverzeichnisse und Dateien von EJ_Uebungsbuch: Dateien ist ein Dateiverzeichnis Datei0 ist eine Datei Datei1 ist eine Datei ... Unterverzeichnisse und Dateien von ...: .appletviewer ist eine Datei .netbeans ist ein Dateiverzeichnis ... Verfuegbare Laufwerke: C:\ ...; E:\ ...; F:\... Lösung 1.3 Die Klasse DateiundVerzeichnisFilter import java.io.*; import java.util.*; public class DateiundVerzeichnisFilter { 80 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen public static void main(String argFile[]) { String[] fListe1, fListe2, fListe3; File[] fListe4; // Das aktuelle Verzeichnis ermitteln File fDir = new File(System.getProperty("user.dir")); // Das aktuelle Verzeichnis nach vorgegebenen Filterdefinitionen // durchsuchen fListe1 = fDir.list(new FilenameFilter() { public boolean accept(File file, String string) { return string.toLowerCase().endsWith(".java"); } }); fListe2 = fDir.list(new FilenameFilter() { public boolean accept(File file, String string) { return string.toLowerCase().endsWith(".class"); } }); fListe3 = fDir.list(new FilenameFilter() { public boolean accept(File file, String string) { return file.isDirectory(); } }); fListe4 = fDir.listFiles(new FileFilter() { public boolean accept(File pathname) { return ((pathname.getName()).equals("test.ser")); } }); // Die Namen von gefilterten Dateien anzeigen System.out.println(".java-Dateien:"); anzeigeArray(fListe1); System.out.println(".class-Dateien:"); anzeigeArray(fListe2); System.out.println("Alle Dateien:"); anzeigeArray(fListe3); System.out.println("Bestimmte Dateien:"); anzeigeArray(fListe4); } // Generische Methode für die Anzeige von unterschiedlichen // Array-Typen public static <T> void anzeigeArray(T[] ein) { // Die Elemente des Arrays werden über eine for-each-Schleife // ausgegeben for(T eingabeArray: ein) { System.out.print(Arrays.asList(eingabeArray)); } System.out.println(); } } 81 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Programmausgaben .java-Dateien: [ByteArrayStreams.java]... .class-Dateien: [ByteArrayStreams.class]... .Alle Dateien: [Ausgabedatei][ByteArrayStream.java]... .Bestimmte Dateien: [C:\...\java7uebungsbuch2sourcecode\kapitel1\test.ser] Lösung 1.4 Die Klasse TestLossofPrecision import java.util.Arrays; // Testklasse für Operationen mit primitiven Datentypen public class TestLossofPrecision { public static Number[] add(Number zahl1, Number zahl2) { Number[] zahlen = new Number[7]; zahlen[0] = zahl1.intValue() + zahl2.intValue(); zahlen[1] = zahl1.floatValue() + zahl2.floatValue(); zahlen[2] = zahl1.doubleValue() + zahl2.doubleValue(); zahlen[3] = zahl1.intValue() + zahl2.floatValue(); zahlen[4] = zahl1.floatValue() + zahl2.doubleValue(); zahlen[5] = zahl1.shortValue() + zahl2.shortValue(); zahlen[6] = zahl1.byteValue() + zahl2.byteValue(); return zahlen; } public static void main(String[] args) { byte[] b = new byte[26]; int int1 = 1, int2 =2; short short1 = new Short("1").shortValue(); short short2 = new Short("2").shortValue(); long long1 = new Long("1").longValue(); long long2 = new Long("2").longValue(); byte byte1 = new Byte("1").byteValue(); byte byte2 = new Byte("2").byteValue(); int int3 = int1 + int2; int short3 = short1 + short2; long long3 = long1 + long2; int byte3 = byte1 + byte2; // Fehler // short short7 = short1 + short2; // byte byte7 = byte1 + byte2; // short short3 = new Short(short1 + short2).shortValue(); // byte byte3 = new Byte(byte1 + byte2).byteValue(); // Korrekte Additionen 82 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen int short4 = (new Short(short1)).shortValue() + (new Short(short2)).shortValue(); int byte4 = (new Byte(byte1)).byteValue() + (new Byte(byte2)).byteValue(); System.out.println(int3 + " " + short3 + " " + long3 + " " + byte3 + " " + short4 + " " + byte4); short short5 = (new Integer(short4)).shortValue(); byte byte5 = (new Byte(""+byte4)).byteValue(); System.out.println(byte5+"*"+ short5); // Der Konstruktor der Klasse Byte und das Rechnen mit byte-Werten System.out.println("Die Grossbuchstaben und ihr 7-Bit " + "ASCII-Key-Code:"); for(int i=65; i<91; i++) { b[i-65] = new Byte(""+i); System.out.print(b[i-65] + " "); } System.out.println(); System.out.println(new String(b)); System.out.println("Die Kleinbuchstaben und ihr 7-Bit " + "ASCII-Key-Code:"); for(int i=65; i<91; i++) { String s = Character.toString(Character.toLowerCase( (char)b[i-65])); System.out.print(" " + s + " "); int zahl = (new Byte(b[i-65])).byteValue()+ (new Integer(32)).byteValue(); b[i-65] = new Byte(""+zahl); System.out.print(b[i-65]); } System.out.println(); // Casting von primitiven Datentypen // Fehler // short short6 = (short)int1 + (short)int2; // byte byte6 = (byte)long1 + (byte)long2; // Korrekt, aber kein Casting erforderlich, int int6 = (int)short1 + (byte)short2; int int7 = (int)byte1 + (short)byte2; // weil eine implizite Konvertierung stattfindet int int8 = short1 + short2; int int9 = byte1 + byte2; System.out.println(int6 + "*" + int7 + "*" + int8+ "*" + int9); // Im Methodenaufruf von add() werden Referenzen auf Objekte von // Unterklassen des Parametertyps übergeben (ein Beispiel // für den "impliziten Polymorphismus") Number[] zahlen1 = add(new Integer(1), new Integer(2)); System.out.println("Ergebnisse der Addition:"+ 83 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Arrays.asList(zahlen1)); Number[] zahlen2 = add(new Float(-0.56f), new Float(2E-2)); System.out.println("Ergebnisse der Addition:"+ Arrays.asList(zahlen2)); Number[] zahlen3 = add(new Double(-0.56d), new Double(2E-2)); System.out.println("Ergebnisse der Addition:"+ Arrays.asList(zahlen3)); Number[] zahlen4 = add(new Short("1"), new Short("2")); System.out.println("Ergebnisse der Addition:"+ Arrays.asList(zahlen4)); Number[] zahlen5 = add(new Byte("1"), new Byte("2")); System.out.println("Ergebnisse der Addition:"+ Arrays.asList(zahlen5)); } } Programmausgaben 3 3 3 3 3 3 3*3 Die Grossbuchstaben und ihr 7-Bit ASCII-Key-Code: 65 66 67 ... ABC... Die Kleinbuchstaben und ihr 7-Bit ASCII-Key-Code: a 97 b 98 c 99 ... 3*3*3*3*3*3 Ergebnisse der Addition:[3, 3.0, 3.0, 3.0, 3.0, 3, 3] ... Lösung 1.5 Die Klasse ByteFileStreams import java.io.*; public class ByteFileStreams { //Schreiben und Lesen von binären Dateien public static void main(String args[]) { byte[] array = new byte[26]; int byteCode; // Das byte-Array initialisieren for(int i=65; i<91; i++) array[i-65] = new Byte(""+i); try { // Ein byteorientierter Stream vom Typ der Klasse FileOutputStream // wird mit einer Datei namens Binärdatei verknüpft FileOutputStream byteFileOut = new FileOutputStream("Binärdatei", true); 84 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen byteFileOut.write(array); // File-Stream schliessen byteFileOut.close(); // Ein byteorientierter Stream vom Typ der Klasse FileInputStream // wird mit der vorher erstellten Datei verknüpft um die // Daten daraus einzeln einzulesen und am Bildschirm anzuzeigen FileInputStream byteFileIn = new FileInputStream("Binärdatei"); while((byteCode = byteFileIn.read()) != -1) System.out.println("byte- und char-Wert der aus der " + " Datei eingelesenen Daten: " + byteCode + "*" + (char)byteCode); byteFileIn.close(); } catch(IOException e) { e.getMessage(); } } } Programmausgaben byte- und char-Wert der aus der Datei eingelesenen Daten: 65*A byte- und char-Wert der aus der Datei eingelesenen Daten: 66*B ... Lösung 1.6 Die Klasse CharFileStreams import java.io.*; public class CharFileStreams { //Schreiben und Lesen von Textdateien public static void main(String args[]) { char[] array = new char[26]; int zeichen; try { // Ein zeichenorientierter Stream vom Typ der Klasse FileWriter // wird mit einer Datei namens Textdatei verknüpft FileWriter charFileOut = new FileWriter("Textdatei"); for(int i=65; i<91; i++) charFileOut.write(i); // File-Stream schliessen charFileOut.close(); // Ein zeichenorientierter Stream vom Typ der Klasse FileReader // wird mit der vorher erstellten Datei verknüpft, um die Daten // in ein char-Array einzulesen und am Bildschirm anzuzeigen 85 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams FileReader charFileIn = new FileReader("Textdatei"); zeichen = charFileIn.read(array); String s = new String(array); System.out.println("Aus der Datei wurden " + zeichen + " Zeichen eingelesen: " + s); // File-Stream schliessen charFileIn.close(); } catch (IOException e) { e.getMessage(); } } } Programmausgaben Aus der Datei wurden 26 Zeichen eingelesen: ABCDEFGHIJKLMNOPQRSTUVWXYZ Hinweise zu den Programmausgaben Es ist nicht garantiert, dass mit der einen read-Anweisung tatsächlich der gesamte Inhalt der Datei in das Array eingelesen wird. Falls die Eingabe so langsam und in so kleinen Blöcken erfolgt, dass beim ersten Lesevorgang weniger als die gesamten 26 Zeichen eingelesen werden, dann würde die Programmausgabe entsprechend weniger Zeichen enthalten. Man müsste eine Schleife von mehreren read-Aufrufen bis zum Dateienbde ausführen, um garantiert den kompletten Dateiinhalt zu erhalten. Lösung 1.7 Die Klasse ByteArrayStreams import java.io.*; public class ByteArrayStreams { public static void main(String args[]) { byte[] array1 = {74, 97, 118, 97}; byte[] array2, array3; String s; int anzBytes, byteCode; try { // Byteorientierte Ausgabe-Streams erzeugen ByteArrayOutputStream byteArrayOut1 = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOut2 = new ByteArrayOutputStream(); // Die Zahlen 1 bis 6 und das Wort Java-Stream in die Streams // schreiben 86 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen for(int i=49; i<=54; i++) byteArrayOut1.write(i); byteArrayOut2.write(array1); // Die Größe eines ByteArray-Stream wächst nach Bedarf; die // hiermit einzeln geschriebenen Bytes werden am Ende der vorher // geschriebenen Arrayelemente nacheinander angefügt byteArrayOut2.write(45); byteArrayOut2.write(83); byteArrayOut2.write(116); byteArrayOut2.write(114); byteArrayOut2.write(101); byteArrayOut2.write(97); byteArrayOut2.write(109); // ByteArrayOutputStreams für die Anzeige am Bildschirm in einen // String konvertieren s = byteArrayOut1.toString(); System.out.println("Inhalt des ersten Output-" + "Stream = " + s); s = byteArrayOut2.toString(); System.out.println("Inhalt des zweiten Output-" + "Stream = " + s); // Ausgabe-Streams kopieren byteArrayOut2.writeTo(byteArrayOut1); s = byteArrayOut2.toString(); System.out.println("Inhalt des kopierten Output-" + "Stream = " + s); // ByteArrayOutputStream in ein byte-Array umsetzen, um damit ein // ByteArrayInputStream zu erzeugen array2 = byteArrayOut2.toByteArray(); // Array für das Speichern von eingelesenen Daten erzeugen array3 = new byte[11]; // Der zweite Stream beinhaltet nur die Bytes von Position 0 bis // 4, d.h. auch nur diese können daraus gelesen werden ByteArrayInputStream byteArrayIn1 = new ByteArrayInputStream(array2); ByteArrayInputStream byteArrayIn2 = new ByteArrayInputStream(array2,0,4); // Einlesen der binären Daten aus den Eingabe-Streams und diese // in Strings bzw. char-Werte umsetzen anzBytes = byteArrayIn1.read(array3); String s1 = new String(array3); System.out.println("Inhalt des ersten Input-Stream = " + s1); System.out.println("Anzahl der aus dem Stream gelesenen" 87 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams +" Bytes = " + anzBytes); while((byteCode = byteArrayIn2.read()) != -1) System.out.println("byte- und char-Wert der aus dem " + " zweiten Input-Stream eingelesenen Daten: " + byteCode + "*" + (char)byteCode); } catch(IOException e) { System.out.println("Fehler: " + e.getMessage()); } } } Programmausgaben Inhalt des ersten Output-Stream = 123456 Inhalt des zweiten Output-Stream = Java-Stream ... byte- und char-Werte der aus der Datei eingelesenen byte- und char-Werte der aus der Datei eingelesenen byte- und char-Werte der aus der Datei eingelesenen byte- und char-Werte der aus der Datei eingelesenen Daten: Daten: Daten: Daten: 74*J 97*a 118*v 97*a Lösung 1.8 Die Klasse CharArrayStreams import java.io.*; public class CharArrayStreams { public static void main(String args[]) { char[] array1 = {'J', 'a', 'v', 'a'}; char[] array2, array3; String string = "Java"; String s; int anzZeichen, zeichen; try { // Zeichenorientierte Ausgabe-Streams erzeugen CharArrayWriter charArrayOut1 = new CharArrayWriter(); CharArrayWriter charArrayOut2 = new CharArrayWriter(); CharArrayWriter charArrayOut3 = new CharArrayWriter(); // Die Zahlen 1 bis 6 und das Wort Java-Stream in die Streams // schreiben for(int i=49; i<=54; i++) charArrayOut1.write(i); charArrayOut2.write(array1); 88 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen // Auch die Größe eines CharArray-Stream wächst nach Bedarf; die // hiermit einzeln geschriebenen Bytes werden am Ende der vorher // geschriebenen Arrayelemente nacheinander angefügt charArrayOut2.write(45); charArrayOut2.write(83); charArrayOut2.write(116); charArrayOut2.write(114); charArrayOut2.write(101); charArrayOut2.write(97); charArrayOut2.write(109); // Das Wort Java in den 3. Stream als String schreiben charArrayOut3.write(string); // CharArrayWriter-Streams für die Anzeige am Bildschirm in einen // String konvertieren s = charArrayOut1.toString(); System.out.println("Inhalt des ersten Output-" + "Stream = " + s); s = charArrayOut2.toString(); System.out.println("Inhalt des zweiten Output-" + "Stream = " + s); s = charArrayOut3.toString(); System.out.println("Inhalt des dritten Output-" + "Stream = " + s); // Ausgabe-Streams kopieren charArrayOut2.writeTo(charArrayOut1); s = charArrayOut2.toString(); System.out.println("Inhalt des kopierten Output-" + "Stream = " + s); // CharArrayWriter-Stream in ein char-Array umsetzen, um damit ein // CharArrayReader-Stream zu erzeugen array2 = charArrayOut2.toCharArray(); // Array für das Speichern von eingelesenen Daten erzeugen array3 = new char[11]; // Der zweite Stream beinhaltet nur die Zeichen von Position 0 // bis 4, d.h., dass auch nur diese im nachhinein daraus gelesen // werden können CharArrayReader charArrayIn1 = new CharArrayReader(array2); CharArrayReader charArrayIn2 = new CharArrayReader(array2,0,4); // Einlesen der Daten aus den Eingabe-Streams und diese // in Strings bzw. char-Werte umsetzen anzZeichen = charArrayIn1.read(array3); String s1 = new String(array3); System.out.println("Inhalt des ersten Input-Stream = " 89 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams + s1); System.out.println("Anzahl der aus dem Stream " + "gelesenen Zeichen = " + anzZeichen); while((zeichen = charArrayIn2.read()) != -1) System.out.println("int- und char-Wert der aus dem" + " zweiten Input-Stream eingelesenen Daten: " + zeichen + "*" + (char)zeichen); // Schließen der Streams, weil dadurch Systemressourcen // freigegeben werden charArrayOut1.close(); charArrayOut2.close(); charArrayOut3.close(); charArrayIn1.close(); charArrayIn2.close(); } catch(IOException e) { System.out.println("Fehler: " + e.getMessage()); } } } Programmausgaben Inhalt des ersten Output-Stream = 123456 Inhalt des zweiten Output-Stream = Java-Stream ... int- und char-Werte der aus der Datei eingelesenen int- und char-Werte der aus der Datei eingelesenen int- und char-Werte der aus der Datei eingelesenen int- und char-Werte der aus der Datei eingelesenen Daten: Daten: Daten: Daten: 74*J 97*a 118*v 97*a Lösung 1.9 Die Klasse UpperCaseOutputFilter import java.io.*; class UpperCaseOutputFilter extends FilterOutputStream { // Konstruktordefinition public UpperCaseOutputFilter(OutputStream outStream) { // Den Konstuktor der Oberklasse aufrufen super(outStream); } // Die write()-Methoden der Oberklasse überschreiben public void write(int b) throws IOException { out.write(b-32); } 90 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen public void write(byte b[]) throws IOException { for(int i=0; i<b.length; i++) out.write(b[i]-32); } } Die Klasse ByteOutputFilterfuerFileStreams public class ByteOutputFilterfuerFileStreams { public static void main(String[] args) { byte[] array = new byte[26]; int byteCode, zeichen; // Das byte-Array initialisieren for(int i=97; i<123; i++) array[i-97] = new Byte(""+i); System.out.println("Ein byte-Array, das in einen FileStream" + " geschrieben wird: " + new String(array)); try { // Einen FilterStream vom Typ der Klasse UpperCaseOutputFilter // zum Umsetzen von Klein- in Großbuchstaben für einen // FileOutputStream erzeugen UpperCaseOutputFilter byteFilterOut1 = new UpperCaseOutputFilter( new FileOutputStream("Filter")); // Das byte-Array schreiben byteFilterOut1.write(array); // Die Daten aus der Datei mithilfe eines FileInputStream // einlesen FileInputStream byteFileIn = new FileInputStream("Filter"); zeichen = byteFileIn.read(array); // und am Bildschirm anzeigen System.out.println("Aus der Datei wurden " + zeichen + " Zeichen eingelesen: " + new String(array)); byteFileIn.close(); } catch(IOException e) { System.out.println("Fehler: " + e.getMessage()); } } } Programmausgaben Ein byte-Array, das in einen FileStream geschrieben wird: abcdefghijklmnopqrstuvwxyz Aus der Datei wurden 26 Zeichen eingelesen: ABCDEFGHIJKLMNOPQRSTUVWXYZ 91 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Lösung 1.10 Die Klasse ByteOutputFilterfuerPrintStream public class ByteOutputFilterfuerPrintStream { public static void main(String[] args) { byte[] array = new byte[26]; int byteCode, zeichen; // Das byte-Array initialisieren for(int i=97; i<123; i++) array[i-97] = new Byte(""+i); System.out.println("Ein byte-Array, das in einen " + "PrintStream geschrieben wird: " + new String(array)); try { // Den von der Klasse FilterOutputStream definierten Null-Filter // testen FilterOutputStream byteFilterOut1 = new FilterOutputStream(System.out); System.out.println("In den PrintStream ohne Filter " + "schreiben: "); byteFilterOut1.write(array); byteFilterOut1.write('a'); byteFilterOut1.write('b'); System.out.println(); // Einen FilterStream vom Typ der Klasse UpperCaseOutputFilter // zum Umsetzen von Klein- in Großbuchstaben für einen // PrintStream erzeugen UpperCaseOutputFilter byteFilterOut2 = new UpperCaseOutputFilter(System.out); System.out.println("In den PrintStream mit einem Filter " + "schreiben: "); byteFilterOut2.write(array); byteFilterOut1.write('a'); byteFilterOut1.write('b'); // Die Streams schließen byteFilterOut1.close(); byteFilterOut2.close(); } catch(IOException e) { System.out.println("Fehler: " + e.getMessage()); } } } 92 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Programmausgaben Ein byte-Array, das in einen PrintStream geschrieben wird: abcdefghijklmnopqrstuvwxyz In den PrintStream ohne Filter schreiben: abcdefghijklmnopqrstuvwxyzab In den PrintStream mit einem Filter schreiben: ABCDEFGHIJKLMNOPQRSTUVWXYZAB Lösung 1.11 Die Klasse UpperCaseFilterWriter import java.io.*; class UpperCaseFilterWriter extends FilterWriter { // Konstruktordefinition public UpperCaseFilterWriter(Writer outStream) { // Den Konstuktor der Oberklasse aufrufen super(outStream); } // Die write()-Methoden der abstrakten Oberklasse implementieren public void write(int c) throws IOException { out.write(Character.toUpperCase((char)c)); } public void write(char c[], int off, int len) throws IOException { // Zwei Alternativen zum Umsetzen von Characters // for(int i=0; i<c.length; i++) // out.write(Character.toUpperCase(c[i])); out.write(String.valueOf(c).toUpperCase()); } public void write(String s, int off, int len) throws IOException { out.write(s.toUpperCase()); } } Die Klasse CharOutputFilterfuerPrintWriter public class CharOutputFilterfuerPrintWriter { public static void main(String[] args) { char[] array = new char[26]; int byteCode, zeichen; // Das char-Array initialisieren for(int i=97; i<123; i++) array[i-97] = (char)i; System.out.println("Ein char-Array, das in einen " + "PrintStream geschrieben wird: " + 93 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams new String(array)); try { // Einen FilterStream vom Typ der Klasse UpperCaseWriteFilter zum // Umsetzen von Klein- in Großbuchstaben für einen PrintStream // erzeugen UpperCaseFilterWriter charFilterOut = new UpperCaseFilterWriter( new OutputStreamWriter(System.out)); System.out.println("In den PrintStream mit einem Filter " + "schreiben: "); charFilterOut.write(array, 0, 26); charFilterOut.write(" array ", 0, 5); charFilterOut.write('a'); charFilterOut.write('b'); charFilterOut.close(); } catch(IOException e) { System.out.println("Fehler: " + e.getMessage()); } } } Programmausgaben Ein char-Array, das in einen PrintStream geschrieben wird: abcdefghijklmnopqrstuvwxyz In den PrintStream mit einem Filter schreiben: ABCDEFGHIJKLMNOPQRSTUVWXYZAB ARRAY AB Lösung 1.12 Die Klasse LowerCaseInputFilter import java.io.*; class LowerCaseInputFilter extends FilterInputStream { // Konstruktordefinition public LowerCaseInputFilter(InputStream in) { // Der Konstruktor initialisiert den Konstruktor der Oberklasse super(in); } // Nachfolgend die Implementierung einer read()-Methode von // FilterInputStream; diese ruft mit in. die read()-Methode der // Klasse auf, welche im Konstruktor als Parameter übergeben wird; // in ist ein als protected definiertes Feld der Oberklasse public int read(byte[] b, int off, int len) throws IOException { int zeichen = 0; zeichen = in.read(b, off, len); 94 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen System.out.println("Zeichen vor der Umsetzung: " + new String(b)); for(int i=0; i<zeichen; i++) { int zahl = (new Byte(b[i])).byteValue() + (new Integer(32)).byteValue(); b[i] = new Byte("" + zahl); } return zeichen; } } Die Klasse ByteInputFilterfuerFileStreams public class ByteInputFilterfuerFileStreams { public static void main(String[] args) { byte[] array = new byte[26]; int zeichen; // Ein zeichenorientierter Stream vom Typ der Klasse FileWriter // wird mit einer Datei namens "CharFilter" verknüpft try { FileOutputStream byteFileOut = new FileOutputStream("ByteFilter"); // Die binären Werte für die Buchstabenvon A bis Z in die Datei // schreiben for(int i=65; i<91; i++) byteFileOut.write(i); // Stream schliessen byteFileOut.close(); // Einen FilterStream vom Typ der Klasse LowerCaseInputFilter // zum Umsetzen von Groß-in Kleinbuchstaben für einen // FileInputStream erzeugen LowerCaseInputFilter byteFilterIn = new LowerCaseInputFilter( new FileInputStream("ByteFilter")); // Den Dateiinhalt einlesen zeichen = byteFilterIn.read(array, 0, 26); String s = new String(array); // und anzeigen System.out.println("Aus der Datei wurden " + zeichen + " Zeichen eingelesen: " + s); // Streams schliessen byteFilterIn.close(); } catch (IOException e) { e.getMessage(); } } } 95 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Programmausgaben Zeichen vor der Umsetzung: ABCDEFGHIJKLMNOPQRSTUVWXYZ Aus der Datei wurden 26 Zeichen eingelesen: abcdefghijklmnopqrstuvwxyz Lösung 1.13 Die Klasse LowerCaseFilterReader import java.io.*; class LowerCaseFilterReader extends FilterReader { // Konstruktordefinition public LowerCaseFilterReader(Reader in) { // Der Konstruktor initialisiert den Konstruktor der Oberklasse super(in); } // Nachfolgend die Implementierung einer read()-Methode von // FilterReader; diese ruft mit in. die read()-Methode der Klasse // auf, welche im Konstruktor als Parameter übergeben wird; // in ist ein als protected definiertes Feld der Oberklasse public int read(char[] c, int off, int len) throws IOException { int zeichen = 0; zeichen = in.read(c, off, len); System.out.println("Zeichen vor der Umsetzung: " + new String(c)); for(int i=0; i<zeichen; i++) c[i] = Character.toLowerCase(c[i]); return zeichen; } } Die Klasse CharInputFilterfuerFileReader public class CharInputFilterfuerFileReader { public static void main(String[] args) { char[] array = new char[26]; int zeichen; // Ein zeichenorientierter Stream vom Typ der Klasse FileWriter // wird mit einer Datei namens "CharFilter" verknüpft try { FileWriter charFileOut = new FileWriter("CharFilter"); // Die Buchstaben von A bis Z in die Datei schreiben for(int i=65; i<91; i++) charFileOut.write(i); // Stream schliessen charFileOut.close(); 96 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen // Einen FilterStream vom Typ der Klasse LowerCaseFilterReader // zum Umsetzen von Groß-in Kleinbuchstaben für einen // FileReader-Stream erzeugen LowerCaseFilterReader charFilterIn = new LowerCaseFilterReader( new FileReader("CharFilter")); // Den Dateiinhalt einlesen zeichen = charFilterIn.read(array, 0, 26); String s = new String(array); System.out.println("Aus der Datei wurden " + zeichen + " Zeichen eingelesen: " + s); // Streams schliessen charFilterIn.close(); } catch (IOException e) { e.getMessage(); } } } Programmausgaben Zeichen vor der Umsetzung: ABCDEFGHIJKLMNOPQRSTUVWXYZ Aus der Datei wurden 26 Zeichen eingelesen: abcdefghijklmnopqrstuvwxyz Lösung 1.14 Die Klasse ByteFileundBufferedStreams import java.io.*; public class ByteFileundBufferedStreams { public static void main(String args[]) { byte[] array = new byte[100000]; int byteCode, zeichen; long anfang, ende; try { // Ein byteorientierter Stream vom Typ der Klasse FileOutputStream // wird mit einer Datei namens Binärdatei verknüpft FileOutputStream byteFileOut = new FileOutputStream("Binärdatei"); anfang = System.currentTimeMillis(); for(int i=0; i<100000; i++) byteFileOut.write(i); ende = System.currentTimeMillis(); System.out.println("Nicht gepuffertes Schreiben: " + anfang + "*" + "*" + ende + "*" + (ende-anfang)); // Ein byteorientierter Stream vom Typ der Klasse // BufferedOutputStream wird mit dem FileOutputStream gekettet 97 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams BufferedOutputStream bufferedOut = new BufferedOutputStream(byteFileOut); anfang = System.currentTimeMillis(); for(int i=0; i<100000; i++) bufferedOut.write(i); ende = System.currentTimeMillis(); System.out.println("Gepuffertes Schreiben: " + anfang + "*" + "*" + ende + "*" + (ende-anfang)); // Ausgabe-Streams schliessen bufferedOut.close(); // Ein byteorientierter Stream vom Typ der Klasse FileInputStream // wird mit der vorher erstellten Datei verknüpft, um die // Daten daraus einzeln einzulesen und am Bildschirm anzuzeigen FileInputStream byteFileIn = new FileInputStream("Binärdatei"); anfang = System.currentTimeMillis(); for(int i=0; i<100000; i++) zeichen = byteFileIn.read(); ende = System.currentTimeMillis(); System.out.println("Nicht gepuffertes Einlesen: " + anfang + "*" + "*" + ende + "*" + (ende-anfang)); // Gepufferter Lesevorgang BufferedInputStream bufferedIn = new BufferedInputStream(byteFileIn); anfang = System.currentTimeMillis(); for(int i=0; i<100000; i++) zeichen = bufferedIn.read(); ende = System.currentTimeMillis(); System.out.println("Gepuffertes Einlesen: " + anfang + "*" + "*" + ende + "*" + (ende-anfang)); // Eingabe-Streams schliessen bufferedIn.close(); } catch(IOException e) { e.getMessage(); } } } Programmausgaben Nicht gepuffertes Schreiben: ... *113 Gepuffertes Schreiben: ...*0 Nicht gepuffertes Lesen: ... *125 Gepuffertes Lesen: ...*0 98 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Lösung 1.15 Die Klasse CharFileundBufferedStreams import java.io.*; public class CharFileundBufferedStreams { public static void main(String args[]) { char[] array = new char[100000]; int byteCode, zeichen; long anfang, ende; try { // Ein zeichenorientierter Stream vom Typ der Klasse FileWriter // wird mit einer Datei namens Textdatei verknüpft FileWriter charFileOut = new FileWriter("Textdatei"); anfang = System.currentTimeMillis(); for(int i=0; i<100000; i++) charFileOut.write(i); ende = System.currentTimeMillis(); System.out.println("Nicht gepuffertes Schreiben: " + anfang + "*" + "*" + ende + "*" + (ende-anfang)); // Ein zeichenorientierter Stream vom Typ der Klasse // BufferedOutputStream wird mit dem FileOutputStream verknüpft BufferedWriter bufferedOut = new BufferedWriter(charFileOut); anfang = System.currentTimeMillis(); for(int i=0; i<100000; i++) bufferedOut.write(i); ende = System.currentTimeMillis(); System.out.println("Gepuffertes Schreiben: " + anfang + "*" + "*" + ende + "*" + (ende-anfang)); // Ausgabe-Streams schliessen bufferedOut.close(); // Ein zeichenorientierter Stream vom Typ der Klasse FileReader // wird mit der vorher erstellten Datei verknüpft, um die Daten // daraus einzeln einzulesen FileReader charFileIn = new FileReader("Textdatei"); anfang = System.currentTimeMillis(); for(int i=0; i<100000; i++) zeichen = charFileIn.read(); ende = System.currentTimeMillis(); System.out.println("Nicht gepuffertes Lesen: " + anfang+"*"+"*"+ende+"*"+(ende-anfang)); // Gepufferter Lesevorgang BufferedReader bufferedIn = new BufferedReader(charFileIn); anfang = System.currentTimeMillis(); 99 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams for(int i=0; i<100000; i++) zeichen = bufferedIn.read(); ende = System.currentTimeMillis(); System.out.println("Gepuffertes Lesen: " + anfang+"*"+"*"+ende+"*"+(ende-anfang)); bufferedIn.close(); } catch(IOException e) { e.getMessage(); } } } Programmausgaben Nicht gepuffertes Schreiben: ... *78 Gepuffertes Schreiben: ...*16 Nicht gepuffertes Lesen: ... *16 Gepuffertes Lesen: ...*0 Lösung 1.16 Die Klasse SelektierenvonTextZeilen import java.io.*; class SelektierenvonTextZeilen extends BufferedReader { String buchstabe = new String("E "); // Konstruktordefinition public SelektierenvonTextZeilen(Reader in) { // Der Konstruktor reicht den übergebenen Stream weiter an die // Oberklasse super(in); } // Die Methode readLine() der Oberklasse überschrieben und // gleichzeitig mit super. Aufrufen, um die Daten zeilenweise zu // lesen; es werden aber nur die Zeilen zurückgegeben, welche mit // dem angegebenen String beginnen public String readLine() throws IOException { String zeile; while(((zeile = super.readLine()) != null) && (!zeile.substring(0,2).equals(buchstabe))); return zeile; } } 100 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Die Klasse SelektierenvonTextZeilenTest public class SelektierenvonTextZeilenTest { public static void main(String args[]) { try { SelektierenvonTextZeilen bufferedIn = new SelektierenvonTextZeilen( new FileReader("Meldungsdatei")); String zeile; BufferedWriter bufferedOut = new BufferedWriter((new FileWriter("Fehlerdatei"))); while((zeile = bufferedIn.readLine()) != null) { bufferedOut.write(zeile, 0, zeile.length()); bufferedOut.newLine(); } bufferedIn.close(); bufferedOut.close(); } catch (Exception e) { e.getMessage(); } } } Programmausgaben 쐽 Anzeige der Meldungsdatei mit: type Meldungsdatei E Datei nicht gefunden E Ein-/Ausgabe-Fehler I dies ist eine Meldungsdatei E Falscher Dateieintrag I Datei wurde bearbeitet 쐽 Anzeige der Fehlerdatei mit: type Fehlerdatei E Datei nicht gefunden E Ein-/Ausgabe-Fehler E Falscher Dateieintrag Lösung 1.17 Die Klasse DataOutputundInputStreams import java.io.*; public class DataOutputundInputStreams { public static void main(String args[]) { byte[] array = new byte[100]; 101 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams // // // // // // // // // // // // // // // // byte einbyte = new Byte("10").byteValue(); char einchar = 'A'; int einint = 123; short einshort = -1234; long einlong = -1234; float einfloat = 123.45F; double eindouble = -123E3; boolean einboolean = true; String einString = "Binaere Datenstroeme"; String einUTFString = "Binaere Datenstroeme"; try { System.out.println(einbyte + " " + einchar + " " + einint + " " + einshort + " " + einlong + " " + einfloat +" " + eindouble + " "+ einboolean); System.out.println(einString); Ein byteorientierter Stream vom Typ der Klasse FileOutputStream wird mit einer Datei namens DateimitprimitivenDatentypen verknüpft FileOutputStream byteFileOut = new FileOutputStream("DateimitprimitivenDatentypen"); und ein DataOutputStream wird mit einem Stream vom Typ der Klasse BufferedOutputStream und mit diesem FileOutputStream gekettet DataOutputStream dataOut = new DataOutputStream( new BufferedOutputStream(byteFileOut)); In die Datei schreiben dataOut.writeByte(einbyte); dataOut.writeChar(einchar); dataOut.writeInt(einint); dataOut.writeShort(einshort); dataOut.writeLong(einlong); dataOut.writeFloat(einfloat); dataOut.writeDouble(eindouble); dataOut.writeBoolean(einboolean); Damit die Daten korrekt gelesen werden können, sollten Strings auf gleiche Art und Weise geschrieben werden, entweder mit // dataOut.writeBytes(einString); // dataOut.writeChars(einString); oder mit dataOut.writeUTF(einUTFString); Ausgabe-Streams schliessen dataOut.close(); Ein byteorientierter Stream vom Typ der Klasse FileInputStream wird mit der vorher erstellten Datei verknüpft und mit einem DataInputStream und einem BufferedInputStream gekettet, um die geschriebenen primitiven Daten daraus 102 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen // einzulesen und am Bildschirm anzuzeigen FileInputStream byteFileIn = new FileInputStream("DateimitprimitivenDatentypen"); DataInputStream dataIn = new DataInputStream( new BufferedInputStream(byteFileIn)); // Daten aus der Datei lesen // Entweder alle Daten oder einzelne einlesen, ansonsten muss der // Stream neu geöffnet werden /* dataIn.readFully(array); for(int i=0; i<100; i++) System.out.println(array[i]); System.out.println("Alle Daten: " + new String(array));*/ einbyte = dataIn.readByte(); einchar = dataIn.readChar(); einint = dataIn.readInt(); einshort = dataIn.readShort(); einlong = dataIn.readLong(); einfloat = dataIn.readFloat(); eindouble = dataIn.readDouble(); einboolean = dataIn.readBoolean(); System.out.println(einbyte + " " + einchar + " " + einint + " " + einshort + " " + einlong + " " + einfloat + " " + eindouble + " "+ einboolean); // Lesen der mit writeChars() gespeicherten Strings // einString = dataIn.readLine(); // System.out.println(einString); // Lesen des UTF-String mit der dafür definierten Klassenmethode System.out.println( "!!" + DataInputStream.readUTF(dataIn)); // oder der überladenen Instanzmethode // einUTFString = dataIn.readUTF(); // System.out.println("??" + einUTFString); // Eingabe-Streams schliessen dataIn.close(); } catch(IOException e) { e.getMessage(); } } } Programmausgaben 10 A 123 -1234 -1234 123.45 -123000.0 true Binaere Datenstroeme 10 A 123 -1234 -1234 123.45 -123000.0 true !!Binaere Datenstroeme 103 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Lösung 1.18 Die Klasse UmlauteSuchen import java.io.*; public class UmlauteSuchen { public static void main(String args[]) { String[] umlaute = {"ä", "ö", "ü", "ß", "Ä", "Ü", "Ö"}; String[] worte ={"Präfix", "Java", "Strom", "Ströme", "Übung", "Größe"}; String buchstaben = "äöüßÄÖÜ"; String wort; int index; try { // Ein byteorientierter Stream vom Typ der Klasse FileOutputStream // wird mit einer Datei namens DateimitUmlauten verbunden FileOutputStream byteFileOut = new FileOutputStream("DateimitUmlauten"); // und ein DataOutputStream wird mit einem byteorientierten // Stream vom Typ der Klasse BufferedOutputStream und mit diesem // FileOutputStream gekettet DataOutputStream dataOut = new DataOutputStream( new BufferedOutputStream(byteFileOut)); // In die Datei schreiben for(int i=0; i<worte.length; i++) dataOut.writeUTF(worte[i]); dataOut.close(); // Einen DataOutputStream für die Ausgabe auf die Konsole // erzeugen DataOutputStream byteOut1 = new DataOutputStream(System.out); System.out.println("Umlaute auf die Konsole schreiben"); // und alle im String buchstaben enthaltene Umlaute anzeigen byteOut1.writeUTF(buchstaben); System.out.println(); // Ein byteorientierter Stream vom Typ der Klasse FileInputStream // wird mit der vorher erstellten Datei verknüpft FileInputStream byteFileIn = new FileInputStream("DateimitUmlauten"); DataInputStream dataIn = new DataInputStream( new BufferedInputStream(byteFileIn)); // Die UTFStrings aus der Datei lesen und auf Umlaute untersuchen while((wort = dataIn.readUTF()) != null) { for(int i=0; i<umlaute.length; i++) { if((index = wort.indexOf(umlaute[i])) != -1) // Alle Strings, die Umlaute enthalten am Bildschirm anzeigen System.out.println("Im Wort " + wort + 104 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen " ist der Umlaut mit dem Unicode-Code-Point " + umlaute[i].codePointAt(0) + " enthalten"); } } dataIn.close(); } catch (Exception e) { e.getMessage(); } } } Programmausgaben Umlaute auf die Konsole schreiben ... Im Wort Pr..fix ist der Umlaut mit dem Unicode-Code-Point 228 enthalten Im Wort Str..me ist der Umlaut mit dem Unicode-Code-Point 246 enthalten ... Hinweise zu den Programmausgaben Mit der Verssion 6 von Java wurde ein neue Klasse Console implementiert, die eine korrekte Anzeige von Umlauten am Bildschirm ermöglicht. Lösung 1.19 Die Klasse ZahlenCharacterundStringFormatierung import java.util.*; public class ZahlenCharacterundStringFormatierung { public static void main(String args[]) { // Deklaration und Initialisierung von primitiven Datentypen double[] array = new double[10]; byte einbyte = new Byte("10").byteValue(); char einchar = 'A'; int einint = 123; short einshort = -1234; long einlong = -1234; float einfloat = 123.45F; double eindouble = -123E3; boolean einboolean = true; String string1 = "Ausgabe Formatierungen"; String string2 = "Formatter"; for(int i=0; i<4; i++) array[i] = -123.4567*i; // Formatter-Instanz erzeugen und daran die Methode format() der // Klasse aufrufen 105 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Formatter forMatter = new Formatter(); // Explizite Indizierung // String-Ausgabe mit dem Format-Spezifizierer %s (%S) forMatter.format("%s mit %s %n", string1, string2); // char-Ausgabe mit dem Format-Spezifizierer %C (%c) forMatter.format("Primitive Datentypen %nchar: %C", einchar); // byte- und short-Werte als hexadezimale bzw. oktale Zahl // mit den Format-Spezifizierer %x (%X) und %o anzeigen forMatter.format(" byte: %x short: %o", einbyte, einshort); // Mit dem Format-Spezifizierer %d kann eine beliebige // Dezimalzahl angezeigt werden forMatter.format(" int: %d long: %X float: %,+f", einint, einlong, einfloat); // %E steht für eine Anzeige von Gleitkommazahlen in // wissenschaftlicher Schreibweise (mit Exponent) forMatter.format(" double: %.10E boolean: %b %n", eindouble, einboolean); // Relative Indizierung forMatter.format("Ein double-Array %n %4$+,3g %3$g %2$+,g" +" %1$g", array[0], array[1], array[2], array[3]); // Normale Indizierung forMatter.format("%n %4$+,3g %3$g %<g %<g", array[0], array[1], array[2], array[3]); System.out.println(forMatter); } } Programmausgaben Ausgabeformatierungen mit Formatter char: A byte: a short: 175456 int: 123 long: FFFFFFFFFFFFFB2E float: +123,449997 double: -1.2300000000E+05 boolean: true ... Lösung 1.20 Die Klasse SuchenundZerlegenvonZeichenketten import java.util.*; import java.util.regex.*; public class SuchenundZerlegenvonZeichenketten { public static void main(String args[]) { // Deklaration und Initialisierung von primitiven Datentypen Scanner scanner; Pattern pa; Matcher ma; 106 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen // // // // // StringBuffer sb = new StringBuffer(); String s = new String(); String charString = "A a B b C c D d E e F f G g"; String intString = "1 2 3 4 5 6 7 8 9"; String doubleString = "1,2 34,5 678,910"; Scanner-Instanzen erzeugen und deren Inhalt zerlegen scanner = new Scanner(charString); System.out.println("Die im String enthaltenen Buchstaben " + " sind: "); while(scanner.hasNext()) System.out.print(scanner.next() + " "); System.out.println(); scanner = new Scanner(intString); System.out.println("Die im String enthaltenen ganzen " + "Zahlen sind: "); while(scanner.hasNextInt()) System.out.print(scanner.nextInt() + " "); scanner = new Scanner(doubleString); System.out.println(); System.out.println("Die im String enthaltenen Gleitpunkt-" + "Zahlen sind: "); while(scanner.hasNextDouble()) System.out.print(scanner.nextDouble() + " "); System.out.println(); Strings erweitern und zusammenfügen charString = charString.concat("$"); intString = intString.concat("&"); s = charString + intString; System.out.println("Zeichen suchen, ersetzen und " + "anfuegen:"); Suchen nach Buchstaben, Zahlen oder Sonderzeichen scanner = new Scanner(s).useDelimiter("\\D+"); while(scanner.hasNextInt()) System.out.print(scanner.nextInt()); System.out.println(); scanner = new Scanner(s).useDelimiter("\\W+"); while(scanner.hasNext()) System.out.print(scanner.next()); System.out.println(); scanner = new Scanner(s).useDelimiter("\\p{javaUpperCase}"); while(scanner.hasNext()) System.out.print(scanner.next()); System.out.println(); Äquivalente Vorgehensweisen für Angabe des Suchmusters // scanner = new Scanner(s).useDelimiter("\\$|\\&"); oder 107 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams pa = Pattern.compile("\\$|\\&"); scanner = new Scanner(s).useDelimiter(pa); while(scanner.hasNext()) System.out.print(scanner.next()); System.out.println(); // Alle Buchstaben von A bis M und a bis m mit ! ersetzen pa = Pattern.compile("[A-M]|[a-m]+"); ma = pa.matcher("PatternMatcherScanner"); String s1 = ma.replaceAll("!"); System.out.print(s1); System.out.println(); // Den Buchstaben a und e ein ! nachstellen pa = Pattern.compile("[ae]+"); ma = pa.matcher("PatternMatcherScanner"); while(ma.find()) ma.appendReplacement(sb, ma.group() + "!"); ma.appendTail(sb); System.out.print(sb); System.out.println(); } } Programmausgaben Die im String enthaltenen Buchstaben sind: A a B b C c D d E e F f G g Die im String enthaltenen ganzen Zahlen sind: 1 2 3 4 5 6 7 8 9 Die im String enthaltenen Gleitkomma-Zahlen sind: 1.2 34.5 678.91 Zeichen suchen, ersetzen und anfügen: 123456789 AaBbCcDdEeFfGg123456789 a b c d e f g$123456789& A a B b C c D d E e F f G g1 2 3 4 5 6 7 8 9 P!tt!rn!!t!rS!nn!r Pa!tte!rnMa!tche!rSca!nne!r Lösung 1.21 Die Klasse ZahlenMatrixmitprintln import java.io.*; import java.util.*; public class ZahlenMatrixmitprintln { public static void main(String args[]) { int n = 5, m = 5; 108 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen double[][] matrix = new double[n][m]; // Formatter-Instanz erzeugen und für die Ausgabe die println()// Methode einfach am immer geöffneten OutputStream System.out, // welcher standardmäßig der Konsole zugeordnet ist, aufrufen Formatter forMatter = new Formatter(); for(int i=1; i<n; i++) { for(int j=1; j<m; j++) { matrix[i][j] = Math.pow(i,j); forMatter.format(" %1d ** %2d = %3.3E", i, j, matrix[i][j]); } } // Äquivalente Vorgehensweisen für die Ausgabe mit der vorher // definierten Formatierung // PrintStream printStream = new PrintStream(System.out); // printStream.println(forMatter); // oder System.out.println(forMatter); } } Programmausgaben 1 ** 1 = 1.000E+00 1 ** 2 = 1.000E+00 1 ** 3 = 1.000E+00 ... 2 ** 1 = 2.000E+00 2 ** 2 = 4.000E+00 2 ** 3 =8.000E+00 ... ... Lösung 1.22 Die Klasse ZahlenMatrixmitFormatter import java.io.*; import java.util.*; public class ZahlenMatrixmitFormatter { public static void main(String args[]) { int n = 5, m = 5; double[][] matrix = new double[n][m]; // In einen PrintStream kann auch mit einem Formatter-Objekt // geschrieben werden PrintStream printStream = new PrintStream(System.out); // Formatter-Instanz erzeugen und eine PrintStream-Instanz im // Konstruktor übergeben; für die Ausgabe muss keine print- oder // write-Methode aufgerufen werden Formatter forMatter = new Formatter(printStream); // Selbstverständlich kann auch die System.out-Instanz übergeben // werden // Formatter forMatter = new Formatter(System.out); for(int i=1; i<n; i++) { 109 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams for(int j=1; j<m; j++) { matrix[i][j] = Math.pow(i,j); forMatter.format(" %1d ** %2d = %3.3E", i, j, matrix[i][j]); } } } } Programmausgaben 1 ** 1 = 1.000E+00 1 ** 2 = 1.000E+00 1 ** 3 = 1.000E+00 ... 2 ** 1 = 2.000E+00 2 ** 2 = 4.000E+00 2 ** 3 =8.000E+00 ... ... Lösung 1.23 Die Klasse ZahlenMatrixmitprintf import java.io.*; public class ZahlenMatrixmitprintf { public static void main(String args[]) { int n = 5, m = 5; double[][] matrix = new double[n][m]; // PrintStream-Instanz erzeugen, um daran die Methode printf() // der Klasse aufzurufen PrintStream printStream = new PrintStream(System.out); for(int i=1; i<n; i++) { for(int j=1; j<m; j++) { matrix[i][j] = Math.pow(i,j); printStream.printf(" %1d ** %2d = %3.3E", i, j, matrix[i][j]); // Weil System.out eine Instanz der Klasse PrintStream ist, kann // die printf()-Methode auch direkt an dieser aufgerufen werden // System.out.printf(" %1d ** %2d = %3.3E", i, j, // matrix[i][j]); } } } } Programmausgaben 1 ** 1 = 1.000E+00 1 ** 2 = 1.000E+00 1 ** 3 = 1.000E+00 ... 2 ** 1 = 2.000E+00 2 ** 2 = 4.000E+00 2 ** 3 =8.000E+00 ... ... 110 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Lösung 1.24 Die Klasse ZahlenMatrixmitPrintWriter import java.io.*; public class ZahlenMatrixmitPrintWriter { public static void main(String args[]) { int n = 5, m = 5; double[][] matrix = new double[n][m]; // Ein PrintWriter-Objekt erzeugen und mit einer // OutputStreamWriter-Instanz zwecks Konvertierung ketten; in // diesem Fall kann auf die Brückenklasse verzichtet werden, weil // die Umwandlung intern im Konstruktor der Klasse PrintWriter // automatisch vorgenommen wird // PrintWriter printStream = new PrintWriter( // new OutputStreamWriter(System.out)); // Die einfachste Art eine PrintWriter-Instanz zu erzeugen PrintWriter printStream = new PrintWriter(System.out); // Die printf()-Methode aufrufen for(int i=1; i<n; i++) { for(int j=1; j<m; j++) { matrix[i][j] = Math.pow(i,j); printStream.printf(" %1d ** %2d = %3.3E", i, j, matrix[i][j]); } } // Ein PrintWriter-Stream muss im Gegensatz zu einem PrintStream // geschlossen werden, damit eine Ausgabe erfolgen kann printStream.close(); } } Programmausgaben 1 ** 1 = 1.000E+00 1 ** 2 = 1.000E+00 1 ** 3 = 1.000E+00 ... 2 ** 1 = 2.000E+00 2 ** 2 = 4.000E+00 2 ** 3 =8.000E+00 ... ... Lösung 1.25 Die Klasse FileWriterundPrintWriter import java.io.*; public class FileWriterundPrintWriter { public static void main(String args[]) { int zeichen; 111 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams // // // // // // // // // // // // // // // // // char[] array1 = {'J', 'a', 'v', 'a', '6', '\n'}; char[] array2 = new char[50]; Ein zeichenorientierter Stream der Klasse FileWriter wird mit der Datei "Printdatei" verknüpft try { FileWriter charFileOut = new FileWriter("Printdatei"); Den FileWriter-Stream mit einem PrintWriter-Stream ketten und mit den Methoden von beiden Streams in die Datei schreiben PrintWriter printStream1 = new PrintWriter(charFileOut); charFileOut.write(array1); printStream1.println(array1); charFileOut.write("FileWriter\nPrintWriter"); charFileOut.write('\n'); printStream1.println("FileWriter\nPrintWriter"); printStream1.printf("%C, %c, %c, %c, %c, %c", array1[0], array1[1], array1[2], array1[3], array1[4], array1[4]); printStream1.println(); Der äußere Stream wird geschloßen printStream1.close(); Die erzeugte Datei lesen FileReader charFileIn = new FileReader("Printdatei"); Ein PrintWriter-Objekt erzeugen und mit einer OutputStreamWriter-Instanz für die Ausgabe zwecks Konvertierung ketten PrintWriter printStream2 = new PrintWriter( new OutputStreamWriter(System.out)); Die Bildschirmausgabe zeigt, dass mit der write()-Methode, im Unterschied zu println(), der String nicht mit dem ZeilenendeZeichen abgeschlossen wird System.out.println("Geschriebene und gelesene " + "Zeichen: "); while((zeichen = charFileIn.read(array2))!= -1) { String s = new String(array2, 0, zeichen); printStream2.printf("%s", s); Ohne dass eine weitere PrintWriter-Instanz ereugt wird, kann die Ausgabe über den Aufruf der printf()-Methode an System.out erfolgen; mit dem Format-Spezifizierer %S werden Großbuchstaben geschrieben System.out.printf("%S", s); } printStream2.close(); Datei schliessen charFileIn.close(); } catch(FileNotFoundException e) { System.out.println("Datei nicht gefunden"); 112 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen } catch(IOException e) { System.out.println(e.getMessage()); } } } Programmausgaben JAVA6 JAVA6 FILEWRITER PRINTWRITER ... J, A, V, A, 6, 6 Java6 ... FileWriter ... J, a, v, a, 6, 6 Lösung 1.26 Die Klasse PrintWriterundStringWriter import java.io.*; import java.util.*; public class PrintWriterundStringWriter { public static void main(String args[]) { int einint = 123; long einlong = -1234; float einfloat = 123.45F; double eindouble = -123E3; boolean einboolean = true; String string1 = "Ausgabe Formatierungen"; String string2 = "Java"; char[] array = {'J', 'a', 'v', 'a', '6', '\n'}; try { // Ein PrintWriter-Objekt erzeugen und mit einer // OutputStreamWriter-Instanz für formatierte Ausgaben von Daten // zwecks Konvertierung ketten PrintWriter printStream = new PrintWriter( new OutputStreamWriter(System.out)); // Formatter-Instanz erzeugen und daran die Methode format() der // Klasse aufrufen Formatter forMatter = new Formatter(); try { 113 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams forMatter.format("double: %.10E boolean: %b %n", eindouble, einint); System.out.println(forMatter); try { // Falsche Anzahl Argumente angegeben printStream.printf("Ein double-Array %n %4$+,3g" +" %3$g %2$+,g" + " %1$g", array[0], array[1]); } catch(MissingFormatArgumentException e) { e.printStackTrace(); traceStack(e); } // Falscher Format-Spezifizierer printStream.printf("%f", string1, string2); printStream.close(); } catch(IllegalFormatConversionException e) { e.printStackTrace(); traceStack(e); } // Falsche Charset-Folge "s" für die Konvertierung angegeben Formatter forMatter1 = new Formatter("Formatierungsdatei", "s"); forMatter.format(" int: %d long: %X float: %c", einint, einfloat); } catch(FileNotFoundException e) { e.printStackTrace(); traceStack(e); } catch(UnsupportedEncodingException e) { e.printStackTrace(); traceStack(e); } } // Methode, die aus dem StackTrace Fehler, Dateiname und die // Zeilennummer, in welcher der Fehler aufgetreten ist, ermittelt public static void traceStack(Exception e) { String dateiName = ""; String fehler = ""; int zeilenNr = 0; int index1 = 0; int index2 = 0; int index3 = 0; // Ein StringWriter-Stream ist ein Ausgabe-Stream in einen String StringWriter stringStream = new StringWriter(); // Diesen mit einem PrintWriter-Stream umwickeln, um rein zu 114 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen // schreiben PrintWriter printStream = new PrintWriter(stringStream); // Den StackTrace in den angelgten Stream ausgeben e.printStackTrace(printStream); // dessen Inhalt als String zurückholen und anzeigen String s = stringStream.toString(); System.out.println("StackTrace = " + s); // Den Index des 1. Doppelpunktes ermitteln index1 = s.indexOf(':'); // Das Ende der Fehlermeldung ermitteln index3 = s.indexOf("at "); // Der String "at" darf nicht in einem Wort vorkommen (er muss // dem Zeichen mit dem Unicode-Code-Point 9 folgen) if((s.codePointAt(index3-1) != 9)) index3 = s.indexOf("at ", index3+1); // Die Fehlermeldung lesen fehler = s.substring(index1+1, index3); fehler = fehler.trim(); // Den Index des 2. Doppelpunktes ermitteln index1 = s.indexOf(':', index1+1); // Das Ende der Zeilennummer suchen index2 = s.indexOf(')', index1); // und diese lesen zeilenNr = Integer.parseInt(s.substring(index1+1, index2)); // Den Anfang des Dateinamens ermitteln index2 = s.lastIndexOf('(', index1); // und diesen lesen dateiName = s.substring(index2+1, index1); // Dateiname und die Nummer der Zeile, in welcher der Fehler // aufgetreten ist, am Bildschirm anzeigen System.out.println("Der Fehler: "+ fehler + " ist in " + " der Datei: " + dateiName + " in der Zeile: " + zeilenNr + " aufgetreten"); } } Programmausgaben double: -1.2300000000E+05 boolean: true java.util.MissingFormatArgumentException: ... Der Fehler: Format specifier ‘+,4$3g’ ist in der Datei PrintWriterundStringWriter.java in der Zeile: 28 aufgetreten ... java.util.IllegalFormatConversionException: ... Der Feh5t ler: f != java.lang.String ist in der Datei PrintWriterundStringWriter.java in der Zeile: 36 aufgetreten ... 115 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams java.util.UnsupportedEncodingException: ... Der Fehler: Format specifier ‘+,4$3g’ ist in der Datei PrintWriterundStringWriter.java in der Zeile: 45 aufgetreten ... Lösung 1.27 Die Klasse UngepufferteTastaturEingaben1 import java.io.IOException; public class UngepufferteTastaturEingaben1 { // Durch den Aufruf der read()-Methode an der System.in-Instanz // können die Eingaben von der Tastatur gelesen werden public static void main(String args[]) { int byteCode; // Einlesen von einzelnen Zeichen System.out.println("Beliebige Zeichen ueber die " + "Tastatur eingeben und Enter druecken: "); try { while((byteCode = System.in.read()) != -1) System.out.println("ASCII-Key-Code und char-Wert des " + "von der Tastatur eingelesenen Zeichens: " + byteCode + "*" + (char)byteCode); } catch(IOException e) { e.getMessage(); } } } Programmausgaben Beliebige Zeichen ueber die Tastatur eingeben und Enter druecken: Wird das Wort »Java« über die Tastatur eingegeben, erfolgen die Ausgaben: ASCII-Key-Code ASCII-Key-Code ASCII-Key-Code ASCII-Key-Code ASCII-Key-Code ASCII-Key-Code und und und und und und char-Wert char-Wert char-Wert char-Wert char-Wert char-Wert des des des des des des von von von von von von der der der der der der Tastatur Tastatur Tastatur Tastatur Tastatur Tastatur eingelesenen eingelesenen eingelesenen eingelesenen eingelesenen eingelesenen Zeichens: 74*J Zeichens: 97*a Zeichens: 118*v Zeichen: 97*a Zeichen: 13* Zeichen: 10* Hinweise zu den Programmausgaben Die Zahlen 13 und 10 spezifizieren den Unicode-CodePoint für die Zeichen ‘\n’ und ‘\r’, welche unter Windows für das Zeilenende gesetzt werden. 116 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Falls die Abfrage in der while-Schleife auf while((byteCode = System.in.read()) != ‘\n’) abgeändert wird, wird die Schleife nach der Betätigung der Enter-Taste verlassen, und die Unicode-Zahl für das Zeichen ‘\n’ wird nicht mehr angezeigt. Lösung 1.28 Die Klasse StandardEingabeKanalUmlenken import java.io.*; public class StandardEingabeKanalUmlenken { public static void main(String args[]) { int byteCode; try { // Umlenken des Standard-Input-Kanals in die Datei "Eingabedatei" FileInputStream byteFileIn = new FileInputStream("Eingabedatei"); System.setIn(byteFileIn); // Einlesen von einzelnen Zeichen aus der Datei while((byteCode = System.in.read()) != -1) // und diese zusammen mit der returnierten int-Zahl am // Bildschirm anzeigen System.out.println("int- und char-Wert des aus der " + " Datei: Eingabedatei eingelesenen Zeichens: " + byteCode + "*" + (char)byteCode); } catch(IOException e) { e.getMessage(); } } } Programmausgaben Wird das Wort »Java« in die Datei »Eingabedatei« geschrieben, erfolgen die Ausgaben: intintintint- und char-Wert des aus der Datei: Eingabedatei eingelesenen Zeichens: 74*J und char-Wert des aus der Datei: Eingabedatei eingelesenen Zeichens: 97*a und char-Wert des aus der Datei eingelesenen Zeichens: 118*v und char-Wert des aus der Datei eingelesenen Zeichens: 97*a Lösung 1.29 Die Klasse UngepufferteTastaturEingaben2 import java.io.IOException; public class UngepufferteTastaturEingaben2 { // Durch den Aufruf der read(byte[] array)-Methode an der 117 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams // System.in-Instanz können die Eingaben von der Tastatur in ein // byte-Array eingelesen werden public static void main(String args[]) { String s; byte[] array = new byte[10]; int zeichen; // Einlesen von Tastatureingeben in ein Array System.out.println("Beliebige Zeichen ueber die " + "Tastatur eingeben und Enter druecken: "); try { // Den Arrayelenmenten werden die von der Tastatur eingelesenen // Daten zugewiesen und ihre Anzahl im Feld zeichen gespeichert zeichen = System.in.read(array); // Das Array in ein String-Objekt umsetzen s = new String(array, 0, zeichen); System.out.println("Von der Tastatur eingelesene " + "Zeichen: " + s); } catch(IOException e) { e.getMessage(); } } } Programmausgaben Beliebige Zeichen ueber die Tastatur eingeben und Enter druecken: Wird das Wort »Java« über die Tastatur eingegeben, erfolgt die Ausgabe: Von der Tastatur eingelesene Zeichen: Java Lösung 1.30 Die Klasse GepufferteTastaturEingaben import java.io.*; public class GepufferteTastaturEingaben { public static void main(String args[]) { // Einlesen von Tastatureingaben mithilfe eines BufferdReader// Streams String s; InputStreamReader tastaturIn = new InputStreamReader(System.in); BufferedReader bufferedIn = new BufferedReader(tastaturIn); System.out.println("Beliebige Zeichen ueber die " + "Tastatur eingeben und Enter druecken: "); try { 118 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen // Die Methode readline() der Klasse BufferedReader aufrufen s = bufferedIn.readLine(); System.out.println("Von der Tastatur eingelesene " + "Zeichen: " + s); } catch(IOException e) { System.out.println(e.getMessage()); } } } Programmausgaben Beliebige Zeichen ueber die Tastatur eingeben und Enter druecken: Wird das Wort »Java« über die Tastatur eingegeben, erfolgt die Ausgabe: Von der Tastatur eingelesene Zeichen: Java Lösung 1.31 Die Klasse StandardFehlerKanalUmlenken import java.io.*; public class StandardFehlerKanalUmlenken { public static void main(String args[]) { byte[] array = new byte[10]; int zeichen; FileInputStream byteFileIn = null; try { // Ist die Ausgabedatei noch nicht vorhanden, wird eine // FileNotFoundException ausgelöst, die mit System.err // auf die Konsole geschrieben wird byteFileIn = new FileInputStream("Ausgabedatei"); } catch(FileNotFoundException e) { // Die Ausgabe von Fehlermeldungen an den Fehlerkanal reichen, // der unter Windows standardmäßig der Konsole zugeordnet ist e.printStackTrace(System.err); System.err.println("Es wurde eine FileNotFoundException " + "ausgeloest"); } try { // Umlenken des Standard-Error-Kanals in eine Datei System.setErr(new PrintStream("StdErrDatei")); // An der Instanz, auf welche die FileInputStream-Referenz verweist, // die read()-Methode aufrufen, um mit einer null-Referenz // einen weiteren Fehler zu erzwingen 119 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams byteFileIn.read(array); } catch(NullPointerException e) { // Fehlermeldungen in die Datei "StdErrDatei" schreiben e.printStackTrace(System.err); System.err.println("Es wurde eine NullPointerException " + "ausgeloest"); } catch(IOException e) { // Fehlermeldungen in die Datei "StdErrDatei" schreiben e.printStackTrace(System.err); System.err.println("Es wurde eine IOException " + "ausgeloest"); } try { // Ist die Ausgabedatei noch nicht vorhanden wird diese hiermit // angelegt FileOutputStream byteFileOut = new FileOutputStream("Ausgabedatei"); // Einlesen von Tastatureingeben in ein Array System.out.println("Beliebige Zeichen ueber die " + "Tastatur eingeben und Enter druecken: "); // Den Arrayelenmenten werden die von der Tastatur eingelesenen // Daten zugewiesen und ihre Anzahl im Feld zeichen gespeichert zeichen = System.in.read(array); // Ausgabe-Stream vom Typ ByteArrayOutputStream erzeugen ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream(); // und die vom Standard-Eingabe-Kanal gelesenen Zeichen // reinschreiben byteArrayOut.write(array); // Ein ByteArrayOutputStream kann mit seiner Methode toString(), // für eine Bildschirmanzeige, in einen String konvertiert werden String s = byteArrayOut.toString(); System.out.printf("ByteArray-Stream in String " + "umgesetzt: %s", s); // Den Inhalt des ByteArrayOutputStream in einen anderen // Ausgabe-Stream schreiben byteArrayOut.writeTo(byteFileOut); byteFileOut.close(); } catch(IOException e) { e.printStackTrace(System.err); System.err.println("Datei nicht vorhanden!"); } } } 120 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Programmausgaben Beliebige Zeichen ueber die Tastatur eingeben und Enter druecken: java.io.FileNotFoundException: Ausgabedatei ... Es wurde eine FileNotFoundException ausgeloest ByteArray-Stream in String umgesetzt: Java 쐽 Ausgabe der Fehlerdatei mit: type StdErrDatei java.lang.NullPointerException at StandardFehlerKanalUmlenken.main... Es wurde eine NullPointerException ausgeloest 쐽 Ausgabe der Ausgabedatei mit: type Ausgabedatei: Java Lösung 1.32 Die Klasse ScannerfuerStringsundStreams import java.io.*; import java.util.*; import java.util.regex.*; public class ScannerfuerStringsundStreams { public static void main(String args[]) { String zeile1 = "1 2 3 4 5"; String zeile2 = "1,2 34,5 678,910 745,11 23,45"; String zeile3 = "A B C D E"; String s; int i = 1, j = 1, k = 1; // Ein zeichenorientierter Stream der Klasse FileWriter wird mit // der Datei "Scannerdatei" verknüpft try { FileWriter charFileOut = new FileWriter("Scannerdatei"); // Den FileWriter-Stream mit einem PrintWriter-Stream ketten // und die vom Programm definierten Strings in die Datei schreiben PrintWriter printStream = new PrintWriter(charFileOut); printStream.println(zeile1); // Zeilenende schreiben printStream.println(); printStream.println(zeile2); printStream.println(); printStream.println(zeile3); printStream.println(); // Der äußere Stream wird geschloßen printStream.close(); // Die so erzeugte Datei mithilfe einer Scanner-Instanz lesen 121 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams FileReader charFileIn = new FileReader("Scannerdatei"); Scanner scStream = new Scanner(charFileIn); while(scStream.hasNextLine()) { // Die gelesene Zeile in einen String speichern s = scStream.nextLine(); System.out.println(s); System.out.println(); // Einer zweiten Scanner-Instanz wird im Konstruktor die // String-Referenz übergeben Scanner scString = new Scanner(s); // Die Methoden der Klasse Scanner für das Zerlegen der // Zeilen aus der Datei aufrufen while(scString.hasNextInt()){ System.out.print(("Zahl" + i + " = ") + scString.nextInt() + " "); i++; } while(scString.hasNextDouble()) { System.out.print(("Zahl" + j + " = ") + scString.nextDouble()+" "); j++; } while(scString.hasNext()) { System.out.print(("Buchstabe" + k + " = ") + scString.next() +" "); k++; } } // Der äußere Stream wird geschloßen scStream.close(); } catch(FileNotFoundException e) { System.out.println("Datei nicht gefunden"); } catch(IOException e) { System.out.println(e.getMessage()); } } } Programmausgaben 1 2 3 4 5 Zahl1 = 1 Zahl2 = 2 Zahl3 = 3 Zahl4 = 4 Zahl5 = 5 1,2 34,5 678,910 745,11 23,45 Zahl1 = 1.2 Zahl2 = 34.5 Zahl3 = 678.910 Zahl4 = 745.11 Zahl5 = 23.45 A B C D E Buchstabe1 = A Buchstabe2 = B Buchstabe3 = C Buchstabe4 = D Buchstabe5 = E 122 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Lösung 1.33 Die Klasse GescannteTastaturEingaben import java.util.*; public class GescannteTastaturEingaben { public static void main(String args[]) { // Einlesen von Tastatureingaben mithilfe der Klasse Scanner String s; Scanner tastaturIn = new Scanner(System.in); System.out.println("Zeichenfolgen ueber die " + "Tastatur eingeben und Enter druecken: "); // Die Methode nextline() der Klasse Scanner aufrufen while(tastaturIn.hasNextLine()) { s = tastaturIn.nextLine(); if(s.equals("Ende")) break; System.out.println("Von der Tastatur eingelesene " + "Zeichen: " + s); } } } Programmausgaben Zeichenfolgen ueber die Tastatur eingeben und Enter druecken: Wird das Wort »Java« über die Tastatur eingegeben, erfolgt die Ausgabe: Von der Tastatur eingelesene Zeichen: Java Lösung 1.34 Die Klasse AusgabenmitderKlasseConsole import java.io.*; public class AusgabenmitderKlasseConsole { public static void main(String args[]) { // Instanz der Klasse Console erzeugen Console konsole = System.console(); String umlaute = "öäüßÖÜÄ"; // Bildschirmausgabe mithilfe von Consolekonsole.printf("Ausgabe mit der Console-Instanz: %s%n", umlaute); 123 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams // und System.out-Instanzen System.out.printf("Ausgabe mit der System.out-Instanz: " + "%s%n", umlaute); } } Programmausgaben Ausgabe mit der Console-Instanz: äöüßÄÖÜ Ausgabe mit der System.out-Instanz: ... Lösung 1.35 Die Klasse EingabenmitderKlasseConsole import java.io.*; public class EingabenmitderKlasseConsole { public static void main(String args[]) { Console konsole = System.console(); String string; // Eine BufferedReader-Instanz mithilfe der Brückenklasse // InputStreamReader mit der System.in-Instanz ketten InputStreamReader tastaturIn = new InputStreamReader(System.in); BufferedReader bufferedIn = new BufferedReader(tastaturIn); konsole.printf("Beliebige Zeichen ueber die " + "Tastatur eingeben und Enter druecken: "); // Die Methode readline() der Klasse Console aufrufen string = konsole.readLine(); konsole.printf("Von der Tastatur eingelesene Zeichen: %s%n", string); System.out.printf("Von der Tastatur eingelesene Zeichen: " + "%s%n", string); System.out.printf("Beliebige Zeichen ueber die " + "Tastatur eingeben und Enter druecken: "); // Die Methode readline() der Klasse BufferedReader aufrufen try { string = bufferedIn.readLine(); } catch(IOException e) { System.out.printf("%n%s", e.getMessage()); } System.out.printf("Von der Tastatur eingelesene Zeichen: " + "%s%n", string); 124 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen konsole.printf("Von der Tastatur eingelesene Zeichen: %s%n", string); } } Programmausgaben Beliebige Zeichen über die Teastatur eingeben und Enter drücken: äöüßÄÖÜ Von der Tastatur eingelesene Zeichen: äöüßÄÖÜ Von der Tastatur eingelesene Zeichen: ... Beliebige Zeichen über die Teastatur eingeben und Enter drücken: äöüßÄÖÜ Von der Tastatur eingelesene Zeichen: äö?ßÄÖÜ Von der Tastatur eingelesene Zeichen: ... Lösung 1.36 Die Klasse Punkt import java.io.*; public class Punkt implements Serializable { private double x; private double y; // Konstruktordefinition Punkt(double x, double y) { this.x = x; this.y = y; } // Zugriffsmethoden public void setX(double x) { this.x = x; } public void setY(double y) { this.y = y; } public double getX() { return x; } public double getY() { return y; } // Instanzmethode für eine Punktanzeige public void anzeige() { System.out.println("Punktkoordinaten: ("+ x +","+ y +")"); } } 125 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams Die Klasse ObjectStreams import java.io.*; public class ObjectStreams { public static void main(String args[]) { Punkt originalPunkt = new Punkt(1.2, 3.4); Punkt serialPunkt; // Ein byteorientierter FileOuputStream wird mit einem // ObjectOutputStream gekettet try { FileOutputStream byteFileOut = new FileOutputStream("Objektdatei"); ObjectOutputStream objectOut = new ObjectOutputStream(byteFileOut); // Objekt serialisieren, d.h. der writeObject()-Methode übergeben objectOut.writeObject(originalPunkt); // Den äußeren Stream schließen objectOut.close(); // FileInputStream mit der vorher erzeugten Datei verknüpfen // und mit einem ObjectInputStream ketten FileInputStream bytefileIn = new FileInputStream("Punktdatei"); ObjectInputStream objectIn = new ObjectInputStream(bytefileIn); // Der Rückgabewert der readObject()-Methode wird in ein Objekt // vom Typ der Klasse Punkt gecastet serialPunkt = (Punkt)objectIn.readObject(); // Den äußeren Stream schließen objectIn.close(); // Am originalen und deserialisierten Objekt die Methode anzeige() // der Klasse Punkt aufrufen originalPunkt.anzeige(); serialPunkt.anzeige(); } catch (Exception e) { e.printStackTrace(); } } } Programmausgaben Punktkoordinaten: (1.2,3.4) Punktkoordinaten: (1.2,3.4) 126 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Lösung 1.37 Die Klasse Kreis import java.io.*; public class Kreis implements Serializable { // Globale Referenzen vom Typ der Klassen Punkt und String private Punkt p; private String s; private double r; // Dieses Instanzfeld soll nicht mit serialisiert werden private transient int instanzZaehler; // Klassenfelder werden nicht serialisiert private static int klassenZaehler; // Konstruktordefinition public Kreis(Punkt p, double r, String s) { this.p = p; this.r = r; this.s = s; instanzZaehler++; klassenZaehler++; } // Zugriffsmethoden public void setR(double r) { this.r = r; } public double getR() { return r; } public void setP(Punkt p) { this.p = p; } public Punkt getP() { return p; } public void setS(String s) { this.s = s; } public String getS() { return s; } // Überschreiben der toString()-Methode der Klasse Object public String toString() { System.out.println("Instanzzaehler: " + instanzZaehler); System.out.println("Klassenzaehler: " + klassenZaehler); // Aufruf der Methode anzeige() der Klasse Punkt p.anzeige(); 127 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams // Die Kreisgleichung als String returnieren return "Kreisgleichung: (x - " +p.getX()+")**2 + "+ "(y - " +p.getY()+")**2 "+" - "+r*r+" = 0"; } } Die Klasse ErweiterteSerialisierung import java.io.*; public class ErweiterteSerialisierung { public static void main(String args[]) { Kreis originalKreis = new Kreis(new Punkt(1.2, 3.4), 1.5, "Serialisierung von Instanzen, deren Klassen " + "Referenzfelder definieren"); // Ein byteorientierter FileOuputStream wird mit einem // ObjectOutputStream gekettet try { FileOutputStream byteFileOut = new FileOutputStream("Kreisdatei"); ObjectOutputStream objectOut = new ObjectOutputStream(byteFileOut); // Objekt der Klasse Kreis serialisieren, d.h. der writeObject()// Methode übergeben objectOut.writeObject(originalKreis); // Den äußeren Stream schließen objectOut.close(); // FileInputStream mit der vorher geschriebenen Datei verknüpfen // und mit einem ObjectInputStream ketten FileInputStream bytefileIn = new FileInputStream("Kreisdatei"); ObjectInputStream objectIn = new ObjectInputStream(bytefileIn); // Der Rückgabewert der readObject()-Methode wird in ein Objekt // vom Typ der Klasse Kreis gecastet Kreis serialKreis = (Kreis)objectIn.readObject(); // Den äußeren Stream schließen objectIn.close(); // Am originalen und deserialisierten Objekt die Methoden // der Klasse Kreis aufrufen System.out.println("Werte der Instanzfelder vor der " + "Serialisierung:"); System.out.println(originalKreis.getS()); System.out.println(originalKreis.toString()); System.out.println("Werte der Instanzfelder nach der " + "Serialisierung:"); System.out.println(serialKreis.getS()); System.out.println(serialKreis.toString()); 128 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen } catch (Exception e) { e.printStackTrace(); } } } Programmausgaben Werte der Instanzfelder vor der Serialisierung: Serialisierung von Instanzen, deren Klassen Referenzfelder definieren Instanzzaehler: 1 Klassenzaehler: 1 Punktkoordinaten: (1.2,3.4) Kreisgleichung: (x - 1.2)**2 + (y - 3.4)**2 -2.25 = 0 Werte derInstanzfelder nach der Serialisierung: Serialisierung von Instanzen, deren Klassen Referenzfelder definieren Instanzzaehler: 0 Klassenzaehler: 1 Punktkoordinaten: (1.2,3.4) Kreisgleichung: (x - 1.2)**2 + (y - 3.4)**2 -2.25 = 0 Lösung 1.38 Die Klasse Schule class Schule { // Die Enumeration NotenListe enum NotenListe { eins(1), zwei(2), drei(3), vier(4), fuenf(5); private int i; // Konstruktordefinition NotenListe(int i) { this.i = i; } // Zugriffsmethoden public int getNote() { return i; } public void setNote(int i) { this.i = i; } } // Die Enumeration SchuelerListe enum SchuelerListe { Mueller, Mayer, Schmidt } 129 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams // Die Enumeration FachListe enum FachListe { Mathematik, Deutsch, Englisch, Physik, Sport } } Die Klasse SchulemitVersionUID import java.io.*; public class SchulemitVersionUID implements Serializable { public String name; public String fach; public int note; // Zum Testen der Versionsnummer-Vergabe beim Serialisieren // wurde der Feldtyp abgeändert public int testVersionUID; // privat static final long serialVersionUID = // 1774228193752720304; // Konstruktordefinition public SchulemitVersionUID(String name, String fach, int note) { this.name = name; this.fach = fach; this.note = note; } // Prüfen, ob die im Konstruktor übergebenen Werte unter den // Aufzählungskonstanten der Enumerationen zu finden sind public void pruefen() { for(Schule.SchuelerListe schueler: Schule.SchuelerListe. values()) { if(schueler.name().equals(name)) System.out.println("Der Schueler: "+ schueler + " ist an dieser Schule "); } for(Schule.FachListe fach: Schule.FachListe.values()) { if(fach.name().equals(this.fach)) System.out.println("Das Fach: "+ fach + " wird an dieser Schule unterrichtet"); } for(Schule.NotenListe note: Schule.NotenListe.values()) { if(note.getNote() == this.note) System.out.println("Die Note: "+ note + " ist zugelassen "); } } } 130 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Die Klasse SerialisierungTest import java.io.*; public class SerialisierungTest { private static SchulemitVersionUID originalSchule[] = new SchulemitVersionUID[2]; private static SchulemitVersionUID serialSchule[] = new SchulemitVersionUID[2]; public static void main(String args[]) { originalSchule[0] = new SchulemitVersionUID("Mayer", "Deutsch", 2); originalSchule[1] = new SchulemitVersionUID("Mueller", "Mathematik", 6); // Die Aufzählungskonstanten der Enumerationen anzeigen System.out.println("Aufzaehlungskonstanten der " + "Enumerationen: "); for(Schule.SchuelerListe schueler: Schule.SchuelerListe.values()) System.out.print(schueler + " "); System.out.println(); for(Schule.FachListe fach: Schule.FachListe.values()) System.out.print(fach + " "); System.out.println(); for(Schule.NotenListe note: Schule.NotenListe.values()) System.out.print(note + " "); System.out.println(); // writeObject(); readObject(); } // Objekt serialisieren public static void writeObject() { try { // Ein byteorientierter FileOuputStream wird mit einem // ObjectOutputStream gekettet FileOutputStream byteFileOut = new FileOutputStream("Schuldatei.ser"); ObjectOutputStream objectOut = new ObjectOutputStream(byteFileOut); // Objekte serialisieren und am originalen Objekt die Methode // pruefen() der Klasse SchulemitVersionUID aufrufen System.out.println("Serialisierte Werte:"); for(int i=0; i<2; i++) { objectOut.writeObject(originalSchule[i]); originalSchule[i].pruefen(); } 131 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams // SerialVersionUID der Klasse SchulemitVersionUID anzeigen System.out.println("SerialVersionUID: " + ObjectStreamClass.lookup(originalSchule.getClass()). getSerialVersionUID()); // Den äußeren Stream schließen objectOut.close(); } catch(IOException e) { e.printStackTrace(); } } // Objekte deserialisieren public static void readObject() { try { // FileInputStream mit der vorher erzeugten Datei verknüpfen // und mit einem ObjectInputStream ketten FileInputStream bytefileIn = new FileInputStream("Schuldatei.ser"); ObjectInputStream objectIn = new ObjectInputStream(bytefileIn); // Der Rückgabewert der readObject()-Methode wird in ein Objekt // vom Typ der Klasse Schule gecastet for(int i=0; i<2; i++) { serialSchule[i] = (SchulemitVersionUID)objectIn. readObject(); // Am deserialisierten Objekt die Methode pruefen() der Klasse // SchulemitVersionUID aufrufen System.out.println("Deserialisierte Werte:"); serialSchule[i].pruefen(); } // SerialVersionUID der Klasse SchulemitVersionUID anzeigen System.out.println("SerialVersionUID: " + ObjectStreamClass.lookup(serialSchule.getClass()). getSerialVersionUID()); // Den äußeren Stream schließen objectIn.close(); } catch(ClassNotFoundException e) { e.printStackTrace(); } catch(IOException e) { e.printStackTrace(); } } } 132 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Programmausgaben Aufzählungskonstanten der Enumerationen: Mueller Mayer Schmidt ... Serialisierte Werte: Der Schueler: Mayer ist an dieser Schule Das Fach: Deutsch wird an dieser Schule unterrichtet Die Note: zwei ist zugelassen Deserialisierte Werte: Der Schueler: Mayer ist an dieser Schule Das Fach: Deutsch wird an dieser Schule unterrichtet Die Note: zwei ist zugelassen Ausgaben nach Änderungen eines Feldtyps und Auskommentieren der writeObject()-Methode: Java.io.InvalidClassException: SchuelermitVersionUID; local class incompatible: stream classdesc serialVersionUID = - 895241860567637238, local class serialVersionUID = 1774228193752720304 ... Lösung 1.39 Die Klasse RandomAccessDatei import java.io.*; public class RandomAccessDatei { private static RandomAccessFile raFile; private static long anzahl; public static void main(String argFile[]) { // Im Konstruktor der Klasse File wird der Pfadname der // Datei als String-Referenz übergeben // File file = new File("C:/EJ_Uebungsbuch1/ // RandomAccessDatei"); // Anschließend muss die Methode createNewFile() aufgerufen // werden, welche die Datei erzeugt // f.createNewFile(); // Instanz der Klasse RandomAccessFile erzeugen // raFile = new RandomAccessFile(file, "rw"); // Diese Vorgehensweise kann vereinfacht werden; die Datei mit // dem Namen RandomAccessDatei wird im aktuellen Verzeichnis // angelegt try { raFile = new RandomAccessFile("RandomAccessDatei", "rw"); // Mehrere Zeilen in die Datei schreiben for(int i=0; i<5; i++) { raFile.writeBytes("ByteZeile" + i+"\n"); raFile.writeChars("CharZeile" + i+"\n"); 133 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams raFile.writeUTF("UTFZeile" + i+"\n"); } // Die Größe der Datei und die Zeigerposition ermitteln anzahl = raFile.length(); // und anzeigen System.out.println("Dateigroesse: " + anzahl + " und Zeigerposition nach dem Schreiben: " + raFile.getFilePointer()); // Den Dateizeiger an den Dateianfang positionieren raFile.seek(0); lesenDatei(); // Anstelle des Textes "ByteZeile" wird über absolute oder // relative Zeigerpositionierung der Text "NeueZeile" geschrieben raFile.seek(0); System.out.print("Die Dateizeigerposition fuer das " + " Schreiben des neuen Textes: "); System.out.print(raFile.getFilePointer() + " "); raFile.writeBytes("NeueZeile"); // Absolute Positionierung /* raFile.seek(45); raFile.writeBytes("NeueZeile"); raFile.seek(90); raFile.writeBytes("NeueZeile"); raFile.seek(135); raFile.writeBytes("NeueZeile"); raFile.seek(180); raFile.writeBytes("NeueZeile"); */ // oder relative Positionierung for(int i=0; i<3; i++) { raFile.skipBytes(36); System.out.print(raFile.getFilePointer() + " "); raFile.writeBytes("NeueZeile"); } System.out.println(); raFile.seek(0); lesenDatei(); raFile.close(); } catch(IOException e) { System.out.println(e.getMessage()); e.printStackTrace(); } } public static void lesenDatei() throws IOException { // Solange das Dateiende nicht erreicht wurde while(raFile.getFilePointer() < anzahl) { // die Textzeilen nacheinander lesen 134 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen String zeile1 = raFile.readLine(); String zeile2 = raFile.readLine(); String zeile3 = raFile.readUTF(); // und anzeigen System.out.println(zeile1+zeile2+zeile3); } } } Programmausgaben Dateigroesse: 225 und Zeigerposition nach dem Schreiben: 225 ByteZeile0 CharZeile0 UTFZeile0 ByteZeile1 CharZeile1 UTFZeile1 ... Die Dateizeigerposition fuer das Schreiben des neuen Textes: 0 45 90 135 NeueZeile0 CharZeile0 UTFZeile0 NeueZeile1 CharZeile1 UTFZeile1 ... Lösung 1.40 Die Klasse DateiundVerzeichnisVerwaltungmitJava7 import java.util.*; import java.io.*; import java.nio.file.*; import java.nio.charset.*; import java.nio.file.attribute.*; import java.net.*; public class DateiundVerzeichnisVerwaltungmitJava7 { public static void main(String argFile[]) { File[] f = new File[7]; Path[] p = new Path[9]; try { // Dateiverzeichnisse und Dateien mit den neuen Möglichkeiten // von Java 7 erzeugen: File-Objekte über die unterschiedlichen // Konstruktoren erzeugen und daran die static-Methode toPath() // der Klasse Files aufrufen, um dieses in ein Path-Objekt // umzusetzen; im Konstruktor der Klasse File wird ein String // als Verzeichnisname übergeben f[0] = new File("C:/EJ_Uebungsbuch1/"); p[0] = f[0].toPath(); // Danach die Methode createDirectory() der Klasse Files // aufrufen, um das Verzeichnis zu erzeugen, falls es noch nicht // existiert; diese Vorgehensweise ermöglicht, vorhandene Programme 135 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams // // // // // // // // // // // // // // // // // // // // // // // // // umzuschreiben, um die neuen Methoden der Klasse Files beim Setzen und Lesen von Dateiattributen bzw. in Reaktionen, wenn Fehler beim Bearbeiten von Dateien auftreten, zu nutzen; die Methode createDirectory() wirft eine FileAlreadyExistsException, falls ein Verzeichnis bzw. eine Datei schon existiert if(Files.notExists(p[0], new LinkOption[]{LinkOption. NOFOLLOW_LINKS})) { Files.createDirectory(p[0]); } Alternativ kann in neuen Programmen ein Path-Objekt mit der Methode getPath() ermittelt werden (dabei muss das angegebene Verzeichnis nicht existieren) und anschließend die Methode createDirectory() aufgerufen werden p[1] = FileSystems.getDefault().getPath( "C:/EJ_Uebungsbuch2/"); if(Files.notExists(p[1], new LinkOption[]{LinkOption. NOFOLLOW_LINKS})) { Files.createDirectory(p[1]); } Im Konstruktor der Klasse File werden das Verzeichnis und der Dateiname als String-Referenz übergeben und danach die Methoden toPath() und createFile() (falls die Datei noch nicht existiert) aufgerufen; das Dateiverzeichnis muss wie hier schon vorhanden sein oder wie in den nachfolgenden Beispielen, bevor die Datei erstellt wird, angelegt werden f[2] = new File("C:/EJ_Uebungsbuch1/", "Datei1"); p[2] = f[2].toPath(); if(Files.notExists(p[2], new LinkOption[]{LinkOption. NOFOLLOW_LINKS})) { Files.createFile(p[2]); } Alternativ kann in neuen Programmen ein Path-Objekt mit der Methode getPath() ermittelt werden (dabei muss das angegebene Verzeichnis nicht existieren) und anschließend die Methoden createDirectory() und createFile() aufgerufen werden; existiert das Directory schon, wird eine FileAlreadyExistsException geworfen p[3] = FileSystems.getDefault().getPath( "C:/EJ_Uebungsbuch3/"); if(Files.notExists(p[3], new LinkOption[]{LinkOption. NOFOLLOW_LINKS})) { Files.createDirectory(p[3]); } Bei diesem Methodenaufruf muss das Directory existieren; existiert die Datei, wird eine FileAlreadyExistsException geworfen if(Files.notExists(FileSystems.getDefault().getPath( 136 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen "C:/EJ_Uebungsbuch3/Datei2"), new LinkOption[]{LinkOption.NOFOLLOW_LINKS})) { Files.createFile(FileSystems.getDefault().getPath( "C:/EJ_Uebungsbuch3/Datei2")); } // Im Konstruktor der Klasse File wird ein abstrakter Pfadname // als File-Referenz übergeben f[3] = new File("C:/EJ_Uebungsbuch4/NIO2"); f[4] = new File(f[3], "Datei3"); p[4] = f[3].toPath(); // Erst nachdem das Verzeichnis angelegt wurde, kann auch die // Datei erstellt werden; existiert das Directory schon, // wird eine FileAlreadyExistsException geworfen if(Files.notExists(FileSystems.getDefault().getPath( "C:/EJ_Uebungsbuch4/NIO2/"), new LinkOption[] {LinkOption.NOFOLLOW_LINKS})) { Files.createDirectory(p[4]); } // Existiert die Datei, wird ebenfalls eine // FileAlreadyExistsException geworfen if(Files.notExists(FileSystems.getDefault().getPath( "C:/EJ_Uebungsbuch4/NIO2/Datei3"), new LinkOption[] {LinkOption.NOFOLLOW_LINKS})) { Files.createFile(f[4].toPath()); } // Absolute Pfadnamen können auch in Teile zerlegt angegeben // werden p[5] = Paths.get("C:","EJ_Uebungsbuch4","NIO2", "Datei4"); // Existiert die Datei, wird eine FileAlreadyExistsException // geworfen if(Files.notExists(FileSystems.getDefault().getPath( "C:/EJ_Uebungsbuch4/NIO2/Datei4"), new LinkOption[] {LinkOption.NOFOLLOW_LINKS})) { Files.createFile(p[5]); } // Der Dateiname kann auch relativ zum aktuellen Verzeichnis // angegeben werden p[6] = FileSystems.getDefault().getPath("Binärdatei"); if(Files.notExists(FileSystems.getDefault().getPath( "Binärdatei"), new LinkOption[] {LinkOption.NOFOLLOW_LINKS})) { Files.createFile(p[6]); } // Außerdem können Shortcuts benutzt werden: Wird in das // übergeordnete Verzeichnis von NIO2 verzweigt, wird da eine // Datei1 gefunden; diese Path-Art kann benutzt werden, um // redundante Fälle auszuschließen, indem die Methode 137 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams // Path.normalize() aufgerufen wird, die jedes redundante Element // inklusive denen, die in "." oder "verzeichnisname/.." // vorkommen, löscht p[7] = Paths.get("C:/EJ_Uebungsbuch1/NIO2/../Datei1"). normalize(); // Oder der Pfadnamen kann mit einer URI erzeugt werden p[8] = Paths.get(URI.create( "file:///C:/EJ_Uebungsbuch1/Datei5")); if(Files.notExists(FileSystems.getDefault().getPath( "C:/EJ_Uebungsbuch1/Datei5"), new LinkOption[] {LinkOption.NOFOLLOW_LINKS})) { Files.createFile(p[8]); } // Weil verschiedene Dateisysteme unterschiedliche Sichten auf // die Art und Weise, wie Dateiattribute ausfindig gemacht // werden, haben können, gruppiert das NIO.2 die Attribute in // Views; der Zugriff auf ein View kann für die Ermittlung von // allen Attributen ("in bulk") mit der Methode readAttributes() // erfolgen und mit der Methode getAttribute() für einzelne // Attribute; mit setAttribute() können einzelne Attribute // gesetzt werden; das NIO.2 definiert 6 Views FileSystem fileSystem = FileSystems.getDefault(); Set<String> views = fileSystem. supportedFileAttributeViews(); System.out.println("Die NIO.2-Views fuer Windows 7: "); for(String view: views) { System.out.print(view + " "); } System.out.println("\nBasic-Attribute"); // Die Attribute aus dem BasicFileAttributeView for(int i=0; i<7; i++) { BasicFileAttributes bfa = Files.readAttributes(p[i], BasicFileAttributes.class); System.out.println(p[i] + " Size: " + bfa.size()); System.out.print(" CreationTime: " + bfa.creationTime()); System.out.print(" LastAccessTime: " + bfa.lastAccessTime()); System.out.print(" LastModifiedTime: " + bfa.lastModifiedTime()); System.out.print(" isDirectory: " + bfa.isDirectory()); System.out.print(" isRegularFile: " + bfa.isRegularFile()); System.out.print(" isSymbolicLink: " + bfa.isSymbolicLink()); System.out.print(" isOther: " + bfa.isOther()); 138 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen // // // // // // // // // System.out.println(); } Lesen eines einzelnen Attributs System.out.println("Das size-Attribut von " + p[6] + " " + Files.getAttribute(p[6], "basic:size", LinkOption.NOFOLLOW_LINKS)); Die Attribute aus dem DosFileAttributeView System.out.println("\nDOS-Attribute"); for(int i=0; i<9; i++) { DosFileAttributes dfa = Files.readAttributes(p[i], DosFileAttributes.class); System.out.println(p[i] + " isReadOnly: " + dfa.isReadOnly()); System.out.print(" isHidden: " + dfa.isHidden()); System.out.print(" isArchive: " + dfa.isArchive()); System.out.print(" isSystem: " + dfa.isSystem()); System.out.println(); } Der Benutzer kann auch eigene Attribute für Verzeichnisse und Dateien setzen (und ggf. wieder löschen) System.out.println("\nUser-Attribute"); for(int i=0; i<9; i++) { UserDefinedFileAttributeView udfav = Files. getFileAttributeView(p[i], UserDefinedFileAttributeView.class); udfav.write("Benutzereintrag" + (i+1), Charset. defaultCharset().encode( "Dies ist ein Benutzereintrag")); for(String name: udfav.list()) { System.out.println(p[i] + "*" + name); } } } catch(NullPointerException | IOException e) { System.out.println(e.getMessage()); e.printStackTrace(); } Dateien umbenennen und löschen; die Methoden renameTo() und delete() der Klasse File werfen keine Exceptions boolean b = f[4].renameTo(new File( "C:/EJ_Uebungsbuch2/DateiNeu")); System.out.println(f[4].getName() + " " + b); b = f[4].delete(); System.out.println(f[4].getName() + " " + b); Die Methode delete() der Klasse Files wirft eine IOException, die es ermöglicht, in Programmen auf Fehler zu reagieren; hier wird die Ausnahme DirectoryNotEmptyException geworfen, weil 139 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams // ein Dateiverzeichnis, in dem sich Dateien befinden, nicht // gelöscht werden kann try { Files.delete(f[3].toPath()); } catch(IOException e) { // System.out.println(e.getMessage()); e.printStackTrace(); } } } Programmausgaben 140 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Lösung 1.41 Die Klasse SynchroneFileChannels import java.io.*; import java.nio.file.*; import java.nio.channels.*; import java.nio.charset.*; import java.nio.ByteBuffer; public class SynchroneFileChannels { // Synchrones Schreiben und Lesen in/aus Dateien mit Channels public static void main(String args[]) { // Zum Erzeugen eines FilChannels alternativ eine Path- bzw. // File-Referenz auf den Pfadnamen ermitteln Path path = Paths.get("C:/EJ_Uebungsbuch3", "Datei2"); // File file = new File("C:/EJ_Uebungsbuch3", "Datei2"); // Zwei verschiedene ByteBuffer-Instanzen für das Schreiben und // Lesen benutzen, um die Werte im Nachhinein anzuzeigen ByteBuffer byteBuffer1 = ByteBuffer.allocate(26); ByteBuffer byteBuffer2 = ByteBuffer.allocate(26); // Ein byte-Array mit den Großbuchstaben des Alphabets // initialisieren byte[] array = new byte[26]; int byteCode; 141 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams for(int i=65; i<91; i++) array[i-65] = new Byte(""+i); // Das Array in den ersten ByteBuffer einhüllen byteBuffer1 = ByteBuffer.wrap(array,0,26); // Einen synchronen FileChannel für das Schreiben in die // Datei bereitstellen try { // Mit Java 1.4 // FileChannel fileChannel = // new FileOutputStream(file).getChannel(); // Alternativ die open()-Methode von Java 7 aufrufen FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.WRITE); // Den Inhalt des Puffers auf den Channel schreiben int anzahl = fileChannel.write(byteBuffer1); System.out.println("Es wurden " + anzahl + " Bytes in " + "die Datei geschrieben"); // FileChannel schließen fileChannel.close(); // Die FileChannel-Instanz zum Lesen eröffnen // Mit Java 1.4 // fileChannel = new FileInputStream(file). // getChannel(); // Alternativ die open()-Methode von Java 7 aufrufen fileChannel = FileChannel.open(path, StandardOpenOption.READ); anzahl = fileChannel.read(byteBuffer2); System.out.println("Es wurden " + anzahl + " Bytes aus " + "der Datei gelesen"); // FileChannel schließen fileChannel.close(); } catch(IOException e) { e.printStackTrace(); } byteBuffer1.flip(); byteBuffer2.flip(); // Für die Anzeige den Pufferinhalt dekodieren System.out.println("Schreibpuffer: " + Charset.forName( System.getProperty("file.encoding")).decode(byteBuffer1)); System.out.println("Lesepuffer: " + Charset.forName( System.getProperty("file.encoding")).decode(byteBuffer2)); byteBuffer1.clear(); byteBuffer2.clear(); } } 142 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 1.15 Lösungen Die Klasse AsynchroneFileChannels import java.io.*; import java.nio.file.*; import java.nio.channels.*; import java.nio.charset.*; import java.nio.ByteBuffer; import java.util.concurrent.*; public class AsynchroneFileChannels { // Asynchrones Schreiben und Lesen in/aus Dateien mit Channels public static void main(String args[]) { // Den Pfadnamen der Datei ermitteln Path path = Paths.get("C:/EJ_Uebungsbuch2", "Datei5"); // Zwei verschiedene ByteBuffer-Instanzen für das Schreiben und // Lesen benutzen, um die Werte kontrollieren zu können ByteBuffer byteBuffer1 = ByteBuffer.allocate(26); ByteBuffer byteBuffer2 = ByteBuffer.allocate(52); // Ein byte-Array mit den Großbuchstaben des Alphabets // initialisieren byte[] array = new byte[26]; int byteCode; for(int i=65; i<91; i++) array[i-65] = new Byte(""+i); // Das Array in den ersten ByteBuffer einhüllen byteBuffer1 = ByteBuffer.wrap(array,0,26); // Eine AsynchronousFileChannel-Instanz für das Schreiben und // Lesen in/aus eine/einer Datei bereitstellen; Channels können // im Gegensatz zu Streams gleichzeitig zum Lesen und Schreiben // eröffnet werden; wird der try-Block aus Java 7 benutzt, // braucht ein Channel nicht explizit geschlossen zu werden, wenn // die entsprechende Klasse das AutoCloseable-Interface // implementiert try(AsynchronousFileChannel asynchronousFileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.READ)){ // Den Inhalt des Puffers ab der angegebenen Position auf den // Channel schreiben Future<Integer> result = asynchronousFileChannel. write(byteBuffer1, 26); // Die Methode isDone() des Future-Interface gibt true zurück, // wenn ein Task komplett abgearbeitet wurde while(!result.isDone()) { System.out.println("Auf das Ende von Write warten"); } // Die write()-Methode liefert in einer Future<Integer>-Instanz // die Anzahl der geschriebenen Bytes, die mit ihrer get()// Methode ermittelt werden kann System.out.println("Anzahl der geschriebenen Bytes: " 143 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203 Kapitel 1 Dateien und Streams + result.get()); Die Bytesequenz aus dem Channel ab der angegebenen Position lesen und in den Puffer übertragen; die read()-Methode liefert in einer Future<Integer>-Instanz die Anzahl der gelesenen Bytes, result = asynchronousFileChannel.read(byteBuffer2, 0); while(!result.isDone()) { System.out.println("Auf das Ende von Read warten"); } // die mit ihrer get()-Methode ermittelt werden kann System.out.println("Anzahl der gelesenen Bytes: " + result.get()); } catch(IOException | InterruptedException | ExecutionException e) { System.out.println("Fehler " + e.getMessage()); } // Die von der abstrakten Oberklasse Buffer geerbte flip()// Methode setzt das Limit (das heißt den Index des ersten // Elements, das nicht mehr gelesen werden soll) auf die aktuelle // Position und danach die Position (das heißt den Index des // Elements, das als Nächstes gelesen werden soll) auf 0 byteBuffer1.flip(); byteBuffer2.flip(); // Für die Anzeige den Pufferinhalt dekodieren System.out.println("Schreibpuffer: " + Charset.forName( System.getProperty("file.encoding")).decode(byteBuffer1)); System.out.println("Lesepuffer: " + Charset.forName( System.getProperty("file.encoding")).decode(byteBuffer2)); // Die Methode clear() setzt das Limit gleich der Puffergröße // und die Position auf 0 byteBuffer1.clear(); byteBuffer2.clear(); } } // // // // Programmausgaben Es wurden 26 Bytes in die Datei geschrieben Schreibpuffer: ABCDEFGHIJKLMNOPQRSTUVWXYZ Lesepuffer: ABCDEFGHIJKLMNOPQRSTUVWXYZ bzw. Auf das Ende von Write warten Anzahl der geschriebenen Bytes: 26 Auf das Ende von Read warten Anzahl der gelesenen Bytes: 52 Schreibpuffer: ABCDEFGHIJKLMNOPQRSTUVWXYZ Lesepuffer: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 144 © des Titels »Java 7« (ISBN 978-3-8266-9203-1) 2012 by Verlagsgruppe Hüthig Jehle Rehm GmbH, Heidelberg. Nähere Informationen unter: http://www.mitp.de/9203