Kurs OPR Objektorientierte Programmierung Lektion: 011-Arbeiten mit Dateien Zürcher Hochschule für Angewandte Wissenschaften Mitglied der Fachhochschule Zürich Version 1.4 Inhaltsverzeichnis 1 Arbeiten mit Dateien 3 1.1 Ziele 3 1.2 Einführung 3 1.3 Zugriff auf Dateien 3 1.4 Sequentieller Filezugriff: Streams 4 1.4.1 Direkter (byte-weiser) sequentieller Zugriff auf eine Datei 4 1.4.2 Gepufferter (blockweiser) Zugriff auf Datei 5 1.5 Java IO-Klassen 6 1.5.1 Erklärungen zu den einzelnen Klassen für das Lesen / Schreiben von Daten: 7 1.5.2 Lesen und Schreiben von Textdateien 8 1.6 Ausgabe auf ein File 10 1.7 Einlesen von einem File 13 1.8 File durchsuchen 15 1.9 Filenamen aussuchen 18 1.10 Umgang mit Dateien 20 1.11 Konsolen-IO 20 1.12 System-Klasse 21 1.13 Konsolenbasierte Anwendungen 22 1.14 Lesen von anderen Sites 24 1.15 Persistente Objekte 26 1.16 Professionelles Programmieren 29 Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 2 1 Arbeiten mit Dateien 1.1 • • • • • 1.2 • • • 1.3 • • Ziele Sie können einem Laien erklären, was eine Datei ist. Sie unterscheiden zwei Zugriffsarten auf eine Datei. Sie können das prinzipielle Vorgehen bei einem File-Zugriff in Java erläutern. Sie kennen mindestens eine Möglichkeit, Textdateien zu lesen und zu schreiben. Sie können mit Files und Directories in Ihren Programmen umgehen. Einführung Daten im Arbeitsspeicher (RAM) gehen beim Ausschalten des Computers verloren. Es braucht deshalb eine Möglichkeit, um diese Daten permanent zu speichern. Æ Zusammengehörige Daten werden zu Dateien (Files) zusammengefasst und als eine Einheit behandelt. Datei (File) − Eine Datei ist nichts anderes als eine Folge von Bytes. − Zusammengehörige Daten werden zu Dateien zusammengefasst, um sie effizient auf permanente Speichermedien ablegen und wieder lesen zu können. (Æ Magnetspeicher) − Es können beliebige Arten von Daten gespeichert werden: − Text, Zahlen − Bilder, Video, Sound − Programme, Objekte − Die Interpretation der Byte-Folge in einer Datei ist Sache des entsprechenden Programms. Zugriff auf Dateien Es bestehen grundsätzlich 2 Möglichkeiten, um auf Dateien zuzugreifen: − sequentieller Zugriff: − Die Daten werden wie bei einem Tonband von vorne beginnend nacheinander eingelesen. − Dieser Zugriff wird auch als Stream-Zugriff bezeichnet. − Dies ist die häufigste Art, auf Dateien zuzugreifen. − Zugriff in beliebiger Reihenfolge, wie z.B. bei Arbeitsspeicher oder einer CD: − Dieser Zugriff wird auch als Random-Zugriff bezeichnet. − Random-Zugriff auf Dateien wird nur in speziellen Fällen benötigt, wie z.B. bei Datenbanken. Zugriffsrechte: − Normalerweise hat der Benützer uneingeschränkte Zugriffsrechte auf seine Dateien (Lesen, Schreiben, Umbenennen, Löschen, Ausführen). − Die Zugriffsrechte können aber vom Besitzer der Datei eingeschränkt werden. − Applets: − Applets erhalten normalerweise vom Browser nur sehr beschränkte Zugriffsrechte auf Files des Computers, auf dem das Applet läuft (aus Sicherheitsgründen). − Signierte Applets oder mit einer entsprechend eingestellten Security-Policy können Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 3 • Applets jedoch auch auf Files zugreifen. − Zugriffe auf Files des Servers, von dem das Applet stammt, sind jedoch erlaubt. Dateien in Java − Es existieren über 50 verschiedene Klassen für die verschiedenen Arten von Filezugriffen. − Die Hierarchie dieser Klassen ist jedoch gut strukturiert und regulär aufgebaut. 1.4 • • • Sequentieller Filezugriff: Streams Sequentieller Zugriff auf Dateien − Beim sequentiellen Zugriff wird ein Byte nach dem anderen von vorne beginnend gelesen bzw. geschrieben. − Dadurch entsteht beim Lesen und Schreiben ein Datenstrom Æ Stream − Einen solchen Stream erhält man auch, wenn Daten statt von einem Speichermedium über eine Kommunikationsleitung übertragen werden. − Da das Lesen einzelner Bytes von einem Speichermedium sehr langsam im Vergleich zum Zugriff auf den Arbeitsspeicher ist, werden normalerweise nur ganze Blöcke von Daten vom Speichermedium gelesen bzw. darauf geschrieben. Die Daten werden dazu im Arbeitsspeicher zwischengelagert (gepuffert). Ablauf beim Lesen eines Files: − File öffnen − Daten nacheinander einlesen und intern abspeichern − File schliessen Ablauf beim Schreiben eines Files: − File öffnen − Daten nacheinander auf das File schreiben − File schliessen 1.4.1 • • • Direkter (byte-weiser) sequentieller Zugriff auf eine Datei Der direkte byte-weise sequentielle Zugriff auf eine Datei ist in Abbildung 1 dargestellt. Vorteil des direkten byte-weisen Zugriffs ist die maximale Kontrolle, die der Programmierer über den Dateizugriff hat, da die Daten ohne Verzögerung auf die Datei geschrieben bzw. von der Datei gelesen werden. Die Nachteile sind zum einen, dass dieser Zugriff langsam und umständlich ist. Zudem wird während des Dateizugriffs die Applikation jedesmal blockiert, bis das Byte gelesen ist. Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 4 Direkter Lesezugriff Direkter Schreibzugriff Datei Datei Arbeitsspeicher Arbeitsspeicher Abbildung 1: Direkter sequentieller Zugriff auf eine Datei 1.4.2 • Gepufferter (blockweiser) Zugriff auf Datei Der gepufferte blockweise Zugriff auf eine Datei ist in Abbildung 2 dargestellt. Gepufferter Lesezugriff Datei Gepufferter Schreibzugriff Datei Block Block Arbeitsspeicher Arbeiten mit Dateien.doc Arbeitsspeicher © 2010 InIT/ZHAW 5 Abbildung 2: Gepufferter blockweiser Zugriff auf eine Datei • • • • 1.5 • Ablauf beim Lesen: − Die Applikation wartet, bis der ganze Puffer gefüllt ist mit Daten von der Datei. Das Füllen des Puffers ist langsam, da es von der Lesegeschwindigkeit des Speichermediums abhängt. Die Applikation kann in der Zwischenzeit aber etwas anderes machen. − Die Applikation liest den Puffer (Block) als Ganzes ein. Dieser Vorgang ist sehr schnell, da es sich im Wesentlichen um das Umkopieren von Daten im Arbeitsspeicher handelt. Ablauf beim Schreiben: − Applikation schreibt einen Block Daten in den Puffer. − Von dort werden die Daten byteweise auf die Datei geschrieben. − Während dieser Zeit kann die Applikation wiederum weiterarbeiten. Vorteile: − Die Applikation ist weniger blockiert durch den Dateizugriff. − Das blockweise Lesen und Schreiben ermöglicht komfortables Lesen und Schreiben grösserer Einheiten (ganze Zahl, ganze Zeile, etc.) Nachteile: − Die Applikation erhält die Daten normalerweise erst, wenn der Puffer gefüllt ist bzw. die Daten werden normalerweise erst auf die Datei geschrieben, wenn der Puffer voll ist. − Auch wenn die Applikation nur ein Byte braucht, wird ein ganzer Block von der Datei gelesen. Java IO-Klassen Die folgende Liste zeigt die wichtigsten Klassen für das Lesen und Schreiben von Daten auf Dateien und andere Medien (Bildschirm, Tastatur, Übertragungskanäle) in der Klassenhierarchie: InputStream - FileIntputStream Lesen von beliebigen Daten (Byte-Streams) Lesen beliebiger Daten von Files OutputStream - FileOutputStream Schreiben beliebiger Daten (Byte-Stream) Schreiben beliebiger Daten auf Files Reader Lesen von Character-Streams (Zeichenströme) (besitzt analoge Subklassen wie InputStream) gepuffertes Lesen, ermöglicht auch zeilenweises Lesen liest Bytes und wandelt sie in Characters um liest Character-Streams von einem File - BufferedReader - InputStreamReader - FileReader Writer - PrintWriter - OutputStreamWriter - FileWriter Arbeiten mit Dateien.doc Schreiben von Character-Streams (analoge Subklassen zu OutputStream) schreibt beliebige Java-Objekte als Strings einen OutputStream wandelt Characters in Bytes und gibt sie aus schreibt Character-Streams auf ein File. © 2010 InIT/ZHAW 6 1.5.1 • Erklärungen zu den einzelnen Klassen für das Lesen / Schreiben von Daten: FileInputStream, FileOutputStream Mit diesen Klassen werden einzelne Bytes von einer Datei gelesen (mit der Methode read()) oder geschrieben (mit write()) wie in Abbildung 3 dargestellt. Datei Datei FileInputStream FileOutputStream Bytes Bytes read() write() Abbildung 3: FileInputStream und FileOutputStream • InputStreamReader, OutputStreamWriter − − − − − Mit diesen Klassen können Byte-Streams in Character-Streams umgewandelt werden (siehe Abbildung 4) Die bei der Umwandlung verwendete Codierung kann gewählt werden. Um Character-Streams von einer Datei zu lesen, müssen diese zuerst mit einem FileInputStream gelesen werden. Um Character-Streams auf eine Datei zu schreiben, müssen die nach der Umwandlung in ByteStreams mit einem FileOutputStream geschrieben werden. Die Klassen InputStreamReader und OutputStreamWriter werden auch verwendet, um auf andere Ein-/Ausgabe-Medien zuzugreifen, z.B. Tastatur, Bildschirm oder Internet (lesen und schreiben von Web-Seiten). Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 7 Datei Datei FileInputStream FileOutputStream Bytes Bytes InputStreamReader Chars OutputStreamWriter Chars read() write() Abbildung 4: Byteweises Lesen und Schreiben einer Datei mit Umwandlung von Bytes zu Characters 1.5.2 • • • Lesen und Schreiben von Textdateien Die häufigste Art von Dateien, die gelesen und geschrieben werden, sind Textdateien. Diese werden meistens zeilenweise gelesen/geschrieben. Aus diesem Grund gibt es spezielle Klassen, um einfach auf solche Textdateien zuzugreifen: − Klasse FileReader liest einzelne Characters von einer Datei. Dabei wird die Standard-Codierung für die Umwandlung von Bytes zu Characters verwendet. − Klasse BufferedReader Liest Characters aus einer Textdatei einzeln oder zeilenweise: − read() liest nächsten Character aus der Datei. − readLine() liest nächste Zeile aus Datei. − Klasse FileWriter schreibt einzelne Characters in eine Datei. Dabei wird die Standard-Codierung für die Umwandlung von Characters zu Bytes verwendet. Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 8 − • Klasse PrintWriter Schreibt Characters in eine Textdatei, einzeln oder zeilenweise: − print(String str) hängt String str ans Ende der Datei − println(String str) hängt String str ans Ende der Datei, und fügt eine Zeilenendemarke ('\n') an. − analoge print-Methoden existieren für int-, float- , ..., doubleZahlen und ganze Objekte. Direktes Lesen und Schreiben von Textdateien − Textdateien können direkt zeichenweise gelesen und beschrieben werden mit den Klassen FileReader und FileWriter (siehe Abbildung 5). FileReader FileWriter Datei Datei Bytes Bytes FileReader FileWriter Chars Chars read() write() Abbildung 5: Zeichenweises Lesen und Schreiben einer Datei • Gepuffertes Lesen und Schreiben von Textdateien ermöglichen die Klassen BufferedReader und PrintWriter (siehe Abbildung 6). • Beachten Sie: − Viele Methoden, die Streams bearbeiten, können IOExceptions werfen. − Diese sind checked, d.h, sie müssen abgefangen werden. Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 9 BufferedReader PrintWriter Datei Datei Bytes Bytes FileReader FileWriter Chars Chars BufferedReader PrintWriter Chars Chars read() readln() print("Hello") print(5) print(true) Abbildung 6: Gepuffertes Lesen und Schreiben von Textdateien 1.6 • Ausgabe auf ein File Beispiel: FileDemo1 Dieses Beispielprogramm demonstriert, wie ein Text in einer TextArea auf eine Datei geschrieben werden kann mittels einem PrintWriter-Objekt. Abbildung 7: FileDemo1 Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 10 import java.io.*; import java.awt.*; import java.awt.event.*; public class FileDemo1 extends Frame implements WindowListener, ActionListener { private TextArea inputTextArea; private Button saveButton; private PrintWriter outFile; public static void main (String [] args) { FileDemo1 demo = new FileDemo1(); demo.setSize(300,400); demo.setVisible(true); } public FileDemo1() { saveButton = new Button("save"); add (saveButton, BorderLayout.NORTH); saveButton.addActionListener(this); inputTextArea = new TextArea(10,50); add (inputTextArea, BorderLayout.CENTER); addWindowListener(this); //for windowClosing } public void actionPerformed(ActionEvent evt) { if (evt.getSource() == saveButton ) { try{ outFile = new PrintWriter(new FileWriter("testout.txt"), true); outFile.print( inputTextArea.getText() ); outFile.close(); } catch (IOException e) { System.err.println("File Error: " + e.toString() ); System.exit(1); } } } public void windowClosing(WindowEvent e) { System.exit(0); } //empty WindowListener Methods public void windowIconified(WindowEvent e) { } public void windowOpened(WindowEvent e) { } Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 11 public public public public void void void void windowClosed(WindowEvent e) { } windowDeiconified(WindowEvent e) { } windowActivated(WindowEvent e) { } windowDeactivated(WindowEvent e) { } } Programm 1: FileDemo1 • Erklärungen zum Programm − Als erstes muss ein PrintWriter-Objekt deklariert werden: private PrintWriter outFile; − Beim Erzeugen des PrintWriter-Objektes wird sodann das entsprechende File neu erzeugt: outFile = new PrintWriter( new FileWriter("filename.ext"), true); − − − − − − − Diese Anweisung öffnet das File mit Namen "filename.ext". Falls es noch nicht existiert, wird es erzeugt. Falls es existiert, wird es überschrieben. Der verwendete Konstruktor des PrintWriters verlangt 2 Argumente: − Ein Writer-Objekt. In der obigen Anweisung wird direkt ein FileWriter-Objekt erzeugt mit new und dieses dem PrintWriter-Konstruktor übergeben. Der Konstruktor von FileWriter verlangt seinerseits den gewünschten Namen des zu erzeugenden Files. − Das 2. Argument besagt, dass der Puffer (Zwischenspeicher) am Ende jeder Zeile auf das File geschrieben werden soll (autoflush = true). − Die erzeugte PrintWriter-Instanz wird schliesslich der Variablen outfile zugewiesen. − Ab Java 5 kann das PrintWriter-Objekt auch direkt mit dem Filenamen erzeugt werden: outfile = new PrintWriter("filename.ext"); Beachten Sie: Der Name des Files "filename.ext" ist der Name, wie er im Directory erscheint. Æ Dieser ist unabhängig vom Variablennamen outfile des PrintWriter-Objekts. PrintWriter ist ein Spezialfall: − Diese Klasse wirft keine Exceptions. − Um herauszufinden, ob beim Schreiben der Datei ein Fehler aufgetreten ist, muss der Programmierer die Methode outfile.checkError() aufrufen. (Wird in diesem Beispiel nicht gemacht) Nach dem Erzeugen und Öffnen der Datei, werden die Daten hineingeschrieben mit: outFile.print(String str); − − Diese Anweisung schreibt den String str auf die Datei outFile. Nachdem das Schreiben der Datei fertig ist, muss das File wieder geschlossen werden mit: outFile.close(); − Arbeiten mit Diese Anweisung schliesst das File outfile. Dateien.doc © 2010 InIT/ZHAW 12 1.7 • Einlesen von einem File Beispiel: FileDemo2 Das Beispielprogramm FileDemo2 zeigt, wie aus einem File gelesen und in eine TextArea geschrieben werden kann. Abbildung 8: FileDemo2 import java.io.*; import java.awt.*; import java.awt.event.*; public class FileDemo2 extends Frame implements WindowListener, ActionListener { private TextArea inputTextArea; private Button loadButton; private BufferedReader inFile; private TextField nameField; public static void main (String [] args) { FileDemo2 demo = new FileDemo2(); demo.setSize(300,400); demo.setVisible(true); } public FileDemo2() { Panel top = new Panel(); loadButton = new Button("load"); top.add(loadButton); loadButton.addActionListener(this); nameField = new TextField(20); top.add(nameField); nameField.addActionListener(this); add ("North", top); inputTextArea = new TextArea("",10,50); add ("Center", inputTextArea); addWindowListener(this); } Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 13 public void actionPerformed(ActionEvent evt) { String fileName; String line; if (evt.getSource() == loadButton) { fileName = nameField.getText(); try { inFile = new BufferedReader(new FileReader(fileName)); inputTextArea.setText(""); // clear the input area while( ( line = inFile.readLine() ) != null) { inputTextArea.append(line+"\n"); } inFile.close(); } catch (IOException e) { System.err.println("Error in file " + fileName + ": " + e.toString() ); System.exit(1); } } } public void windowClosing(WindowEvent e) { System.exit(0); } //empty WindowListener Methods . . . } Programm 2: FileDemo2 • Erklärungen zum Programm − Um die Textdatei zu Lesen wird zuerst ein BufferedReader-Objekt deklariert: private BufferedReader inFile; − Das BufferedReader-Objekt wird mit folgender Anweisung geöffnet: inFile = new BufferedReader(new FileReader(filename)); − Konstruktor von BufferedReader verlangt ein Reader-Objekt. − Dieses wird mit new FileReader(fileName) erzeugt und gerade übergeben. Dabei wird die Datei fileName geöffnet. − Wird die Grösse des Lesepuffers nicht angegeben, so ist sie standardmässig 8K Zeichen. − Die Daten werden nach dem Öffnen der Datei zeilenweise eingelesen mit folgender Schleife: while ((line = inFile.readLine()) != null){ inputTextArea.append(line+"\n"); Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 14 } − − − − − readLine gibt die gelesene Zeile (ohne \n) zurück oder null, falls das Ende des Files (EOF) erreicht worden ist. Die gelesene Zeile wird der Variable line zugewiesen. Danach wird line im while-Schleifenkopf auf null getestet. Klammern sind notwendig, damit die Zuweisung zu line vor dem Vergleich erfolgt. Die Schleife könnte ausführlich auch so geschrieben werden: line = inFile.readLine(); while (line != null){ inputTextArea.append(line+"\n"); line = inFile.readLine(); } − Das File muss nach dem Lesen geschlossen werden mit der Anweisung: inFile.close(); 1.8 • File durchsuchen Beispiel: FileSearch Gegeben sei eine Datei "Punktzahlen.dat", die mit einem gewöhnlichen Editor erstellt worden ist. Der Inhalt der Datei sieht folgendermassen aus Muster, 10, 13 Meier, 40, 36 Müller, 34, 19 • Aufgabe: Analysieren Sie das Programm FileSearch (Programm 3). Beschreiben Sie in Stichworten, wie das Programm genau abläuft. Die Bildschirmdarstellung des Programms ist in unten stehender Abbildung 9 dargestellt. Abbildung 9: Bildschirmdarstellung des Programms FileSearch • Ihre Erklärungen: Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 15 import import import import java.io.*; java.awt.*; java.util.*; java.awt.event.*; // StringTokenizer public class FileSearch extends Frame implements ActionListener, WindowListener { private BufferedReader inFile; private Button searchButton; private TextField result1Field, result2Field, personField; private TextField fileNameField, errorField; private String fileName; public static void main (String [ ] args) { FileSearch search = new FileSearch(); search.setSize(400,400); search.setVisible(true); } public FileSearch() { setLayout(new FlowLayout() ); errorField= new TextField("Type the File name:"); errorField.setEditable(false); add(errorField); fileNameField = new TextField(20); fileNameField.setText(""); add(fileNameField); searchButton = new Button("Search"); add(searchButton); searchButton.addActionListener(this); add(new Label("Type Name:")); personField = new TextField(20); personField.setText(""); add(personField); add( new Label("Result1:")); result1Field = new TextField(5); result1Field.setEditable(false); add(result1Field); add (new Label("Result2:")); result2Field= new TextField(5); result2Field.setEditable(false); add(result2Field); this.addWindowListener(this); } Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 16 public void actionPerformed(ActionEvent evt) { if (evt.getSource() == searchButton) { fileName = fileNameField.getText(); try { inFile = new BufferedReader(new FileReader(fileName)); } catch (IOException e) { errorField.setText("Can't find file "); return; } errorField.setText("Type the file name:"); // now read the file try { String line; boolean found = false; while (( ( line = inFile.readLine() ) != null) && (! found)) { // tokens split on commas, spaces StringTokenizer tokens = new StringTokenizer(line," ,"); String nameInFile = tokens.nextToken(); if (personField.getText().equals(nameInFile)) { found = true; result1Field.setText(tokens.nextToken() ); result2Field.setText(tokens.nextToken() ); } } inFile.close(); } catch (IOException e) { System.err.println("Error reading file " + fileName+": " + e.toString() ); System.exit(1); } } } // WindowListener methods - all needed! public void windowClosing(WindowEvent e) { System.exit(0); } //empty WindowListener Methods ... } Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 17 Programm 3: FileSearch 1.9 • Filenamen aussuchen Heutzutage ist es üblich, dass der Benützer eines Programms beim Öffnen oder Abspeichern einer Datei ein Auswahlwahlfenster, wie unten abgebildet, mit den vorhandenen Dateien bekommt. Dieses Dialogfenster wird in Java durch die Klasse FileDialog zur Verfügung gestellt. Abbildung 10: Programm FileDialogDemo • Beispiel: FileDialogDemo In diesem Beispielprogramm wird gezeigt, wie ein FileDialog-Fenster erzeugt und wie die Auswahl des Benützers vom Programm aus abgefragt werden kann. import java.io.*; import java.awt.*; import java.awt.event.*; public class FileDialogDemo extends Frame implements ActionListener, WindowListener { private Button loadButton; private FileDialog getNameBox; private TextField nameField; public static void main (String [] args) { FileDialogDemo demo = new FileDialogDemo(); demo.setSize(500,400); demo.setVisible(true); } public FileDialogDemo() { setLayout( new FlowLayout() ); loadButton = new Button("load"); Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 18 add(loadButton); loadButton.addActionListener(this); nameField = new TextField(30); add(nameField); addWindowListener(this); // for windowClosing } public void actionPerformed(ActionEvent evt) { String fileName, dirName; if (evt.getSource() == loadButton) { getNameBox = new FileDialog(this, "get Name", FileDialog.LOAD); getNameBox.setVisible(true); // display the name fileName = getNameBox.getFile(); dirName = getNameBox.getDirectory(); nameField.setText(dirName + fileName); } } public void windowClosing(WindowEvent e) { System.exit(0); } //empty WindowListener Methods ... } Programm 4: FileDialogDemo • Erklärungen zum Programm − Deklaration einer File-Dialogbox: private FileDialog getNameBox; − Erzeugen der Dialogbox: getNameBox = new FileDialog(this,"get a name",FileDialog.LOAD); getNameBox.setVisible(true); − − Parameter des Konstruktors: − Frame, in dem die Dialog-Box erzeugt wird − Titel der Dialog-Box − Dialog-Art – FileDialog.LOAD Æ um eine Datei zu öffnen – FileDialog.SAVE Æ um eine Datei zu speichern − es wird ein sogenannter modaler Dialog erzeugt, d.h.: sobald setVisilble(ture) aufgerufen wird, blockiert das Programm, bis eine Datei ausgewählt wurde. Den Namen des ausgewählten Files oder das aktuelle Verzeichnis erhält man mit den Anweisungen: String fileName, dirName; Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 19 fileName = getNameBox.getFile(); dirName = getNameBox.getDirectory(); 1.10 Umgang mit Dateien • Um Dateien als Ganzes zu bearbeiten, stellt Java die Klasse File zur Verfügung. • Da Directories ebenfalls (spezielle) Files sind, können auch Directories mit dieser Klasse bearbeitet werden. Instanzierung eines File-Objektes: • File myFile = new File(String absFilename); − − − • erzeugt ein File-Objekt, nicht das File selbst. absFilename: Filename samt absolutem Pfad (z.B. c:\java\demo.java) Um einen einzelnen Backslash ("\") in einem String einzugeben, schreibt man zwei Backslash nacheinander Æ "\\" Methoden der Klasse File: String getPath() String getAbsolutePath() boolean exists() boolean isDirectory() boolean isFile() boolean canRead() boolean canWrite() boolean delete() long length() String[] list() long lastModified() boolean mkdir() boolean renameTo(File name) gibt relativen Pfad des Files zurück gibt absoluten Pfad des Files zurück Testet, ob File existiert oder nicht Testet, ob File ein Directory ist Testet, ob es ein File (kein Directory) ist gibt false zurück, falls File nicht existiert ist Lesen des Files erlaubt ist Schreiben erlaubt Löscht das File. Gibt true zurück, falls File erfolgreich gelöscht wurde gibt die File-Grösse in Bytes zurück gibt Liste von Filenamen des Directory zurück (null falls File kein Directory ist) Zeitpunkt der letzten Modifikation (Zeit in ms seit Zeitursprung, nur für Vergleiche geeignet) erzeugt ein Verzeichnis anhand des Pfades des Files benennt File um zu name. Gibt true zurück, falls erfolgreich 1.11 Konsolen-IO • Historische Entwicklung von User-Interfaces: − Am Anfang gab es nur Terminals/Konsolen − Die Ein-/Ausgabe war dementsprechend nur textbasiert möglich: − Eingabe per Keyboard − Ausgabe auf Bildschirm − Später kamen einfache Menus dazu: − Diese boten eine Auswahlmöglichkeit aus einer Befehls-Liste via Cursor-Tasten. − Schliesslich kamen die graphischen Benützerschnittstellen (GUI) auf: − Diese bieten neue zusätzliche Eingabenmöglichkeiten per Maus. Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 20 • Software mit Kommandozeilen-Interface sind aber auch heute noch verbreitet − Sie ermöglichen bespielsweise, dass der Output von einem Programm in einem anderen als Input verwenden werden kann (sogenanntes Piping). − Es erlaubt auch das Ausführen von Skripts, und damit das Automatisieren von sich wiederholenden Tasks (z.B. Backup). − Dieses Interface wird häufig auch von Systemadministration gebraucht. − In Java sind Kommandozeilen-Interfaces einfach realisierbar mit speziellen Streams, die in der System-Klasse bereits vordefiniert sind. 1.12 System-Klasse • • • • • Die System-Klasse stellt eine plattformunabhängige Schnittstelle zu den Betriebssystemfunktionen zur Verfügung. Sie enthält nur Klassenvariablen und Klassenmethoden. Sie kann nicht instanziert werden. Sie stellt unter anderem 3 Streams zur Verfügung: − System.in − System.out − System.err System.in − − • Dies ist ein InputStream-Objekt. Es muss nicht erzeugt werden, sondern existiert schon. Es kann für das direkte Einlesen von Tastatureingaben verwendet werden, aber auch für Inputs von Pipes. − Mit dem Objekt System.in kann der Programmierer nur byteweise Daten einlesen. Das Einlesen von der Kommandozeile geht viel komfortabler mit einem BufferedReader: private BufferedReader keyboard; keyboard = new BufferedReader( new InputStreamReader(System.in),1); − − Die Puffergrösse des BuffereReader wird normalerweise auf 1 gesetzt Æ So entsteht keine Verzögerung nach dem Drücken der Return-Taste. BufferedReader wartet nämlich bei einem readLine() bis der Puffer voll ist, bevor er den ganzen Puffer weitergibt. Nachdem der BufferedReader aufgesetzt ist, kann man zeilenweise von der Tastatur lesen mit: String line = keyboard.readLine(); • System.out − ist ein PrintStream-Objekt (Diese Klasse wurde ab Java 1.1 abgelöst durch die Klasse PrintWriter). − Das System.out-Objekt muss ebenfalls nicht erzeugt werden, weil es bereits existiert. − Methoden − print(String str), println(String str) str kann auch mehrere Zeilen enthalten − Analoge print-Methoden existieren für int, float, ... und für Objekte − System.out.flush() gibt Puffer nach einem print-Befehl auf den Bildschirm aus, auch wenn Puffer noch nicht voll ist. Dies braucht man, wenn das Programm einen Prompt an den Benützer ausgeben will und Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 21 − • • der Benützer die Eingabe gerade nach dem Prompt auf der gleichen Zeile machen soll. Dieser Stream kann ebenfalls umgeleitet werden (Piping). System.err: gleich wie System.out, aber dieser Stream kann nicht umgeleitet werden. System.exit(int errorCode): − − beendet Programm augenblicklich und gibt errorCode an Betriebssystem zurück. Konvention für errorCode: − 0 Normale Beendigung − != 0 Abbruch nach Fehler 1.13 Konsolenbasierte Anwendungen • Beispielprogramm: Finder Das Programm Finder sucht einen String in einer Datei. Es gibt alle Zeilen aus, wo der gesuchte String vorkommt zusammen mit der vorherigen und der nachfolgenden Zeile (siehe Abbildung 11). − Der Filename wird als Command-Line-Argument übergeben − Für das Einlesen des gesuchten Strings erzeugt das Programm in der Methode prompt(...) einen Prompt mit einer entsprechenden Meldung. Der Benützer kann dann, den gesuchten String nach dem Prompt eingeben. − In der Figur ist der Dialog mit dem Programm dargestellt, um den String „if“ in der Datei „Finder.java“ zu suchen. Es wurden zwei Zeilen mit „if“ gefunden. Abbildung 11: Finder import java.io.*; public class Finder { private String line1, line2, line3; private BufferedReader keyboard, inStream; public static void main (String [] Finder aFind = new Finder(); aFind.doSearch(args[0]); } args) { private void doSearch(String fileName) { keyboard = new BufferedReader(new Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 22 InputStreamReader(System.in),1); String wanted = prompt("Type string to find:"); line1 = ""; line2 = ""; try { inStream = new BufferedReader(new FileReader(fileName)); while ((line3 = inStream.readLine()) != null) { if ( line2.indexOf(wanted) >= 0 ){ displayLine(); } // advance to the next group of 3 line1 = line2; line2 = line3; // and get new line3 from file... } //check the last line line3 = ""; //replace null value with "" if (line2.indexOf(wanted) >= 0){ displayLine(); } inStream.close(); } catch (IOException e) { System.err.println("Error in Finder: " + e.toString()); System.exit(1); } } private void displayLine() { System.out.println("<<------------ context:"); System.out.println(line1); System.out.println(line2); System.out.println(line3); System.out.println(" ------------->>"); System.out.println(""); } private String prompt(String message) { String reply = ""; try { System.out.print(message); System.out.flush(); reply = keyboard.readLine(); } catch (IOException e) { System.out.println("Keyboard input " + System.exit(2); } Arbeiten mit Dateien.doc © 2010 InIT/ZHAW e.toString() ); 23 return reply; } } Programm 5: Finder 1.14 Lesen von anderen Sites • • • Das Lesen von anderen Sites geht so leicht wie das Lesen von lokalen Files. Was es dazu braucht, ist die Angabe des URL (Uniform Resource Locator) des gewünschten Files. Beispiel: TinyBrowser − Das Programm TinyBrowser stellt eine HTML-Seite als Text dar (Abbildung 12).. − Das Programm fragt vom Benützer eine URL ab. Es nimmt dann Verbindung auf zu dieser URL und gibt den Text, der von dort geschickt wird (z.B. eine HTML-Seite), auf die Konsole aus. Abbildung 12: TinyBrowser Figur 27: import java.io.*; import java.net.*; public class TinyBrowser { private BufferedReader inStream, keyboard; public static void main (String [] args) { TinyBrowser aBrowser = new TinyBrowser(); aBrowser.fetch(); } private void fetch() { String urlString = ""; String line; keyboard = new BufferedReader(new InputStreamReader(System.in),1); try { urlString = prompt (" Type a URL address (e.g http://java.sun.com/) :"); // create a link to a URL URL urlAddress = new URL(urlString); URLConnection link = urlAddress.openConnection(); inStream = new BufferedReader(new InputStreamReader(link.getInputStream())); Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 24 while ((line = inStream.readLine()) != null) { System.out.print(line); } } // try catch (MalformedURLException e) { System.err.println(urlString + e.toString()); System.exit(2); } catch (IOException e) { System.err.println("Error in accessing URL: "+ e.toString()); System.exit(1); } } // fetch private String prompt(String message) { String reply = ""; try { System.out.print(message); System.out.flush(); reply = keyboard.readLine(); } catch (IOException e) { System.out.println(" Keyboard input " + e.toString() ); System.exit(2); } return reply; } } Programm 6: TinyBrowser • Methoden im Zusammenhang mit URLs (dazu muss java.net.* importiert werden): − URL urlAddress = new URL(urlstring); − − − − URLConnection link = urlAddress.openConnection(); − − gibt den gewünschten URL zurück. urlstring hat die Form Protokollname:Ressourcenname In Java 2 werden die Protokolle http und ftp unterstützt. öffnet die Verbindung zur gewünschten Ressource. link.getInputStream() − − Arbeiten mit gibt einen InputStream zurück, der von der geöffneten Verbindung Daten einliest. Dieser Stream wird mit dem InputStreamReader verarbeitet. Dateien.doc © 2010 InIT/ZHAW 25 1.15 Persistente Objekte • • • • • • • • • • Bei fast jeder realen Applikation besteht das Bedürfnis, den aktuellen Zustand der Applikation abspeichern zu können. Da der Zustand eines objektorientierten Programms in Objekten dezentral gespeichert ist, läuft obige Forderung darauf hinaus, Objekte persistent abspeichern und wieder laden zu können. Zu diesem Zweck bietet Java die Möglichkeit, Objekte in einen Byte-Stream zu verwandeln, der dann zum Beispiel auf eine Datei gespeichert werden kann. Dieser Vorgang wird Serialisierung (serialization) eines Objekts genannt. Der abgespeicherte Byte-Stream kann später wieder eingelesen und daraus wieder ein identisches Objekt erzeugt werden Dieser Vorgang wird Deserialisierung (dezerialization) genannt. Bei der Serialisierung eines Objekts werden alle notwendigen Attribute in einen Byte-Stream verpackt. Falls ein Attribut eine Referenz auf eine zweites Objekt darstellt, wird auch dieses Objekt serialisiert und in den Byte-Stream gepackt. Dies führt dazu, dass mit der Serialisierung eines Objekts ganze Objekt-Graphen serialisiert werden. Die Serialisierung von Objekten dient nicht nur zum Speichern von Objekten. Damit können Objekte auch über Kommunikationsleitungen (z.B. Internet) übertgragen werden. Für die Serialisierung stellt Java den ObjectOutputStream zur Verfügung. Dieser verfügt über die Methode writeObject(Object obj), mit der Objekte direkt serialisiert werden können. Für das Einlesen eines serialisierten Objekts stellt die Klasse ObjectInputStream die Methode Object readObject() zur Verfügung. Damit ein Objekt serialisierbar ist, muss es das Interface Serializable implementieren. Dieses Interface verlangt keine Methoden, die implementiert werden müssen. Es zeigt lediglich an, dass das entsprechende Objekt für die Serialisierung geeignet ist. Beispiel: − Das Programm 7: BankApplikation zeigt einen Auszug aus der Klasse BankApplikation. Dieser enthält zwei Methoden um jedes Konto im Konto-Array konto einzeln abzuspeichern und wieder zu lesen. − Um die Konto-Objekte abzuspeichern, wird zuerst das FileOutputStream-Objekt ostream erzeugt. Dadurch wird eine neue Datei filename im Verzeichnis dirname angelegt. − Mit Hilfe des FileOutputStream-Objekts ostream wird sodann ein ObjectOutputStream-Objekt erzeugt. − In einer Schleife wird schliesslich ein Konto-Objekt nach den anderen in den Object-Stream und damit auf die Datei geschrieben mit Hilfe der Methode writeObject(Object obj) des ObjectStream-Objekts. Da der Parameter dieser Methode vom Typ Object ist, können der Methode Objekte beliebiger Klassen übergeben werden. − Beim Laden der Objekte wird ein FileInputStream-Objekt erzeugt, dem ein ObjectInputStream-Objekt im Konstruktor übergeben wird. − Danach kann in einer Schleife wieder ein Objekt nach dem anderen mit Hilfe der vom ObjectInputStream-Objekt zur Verfügung gestellten Methode Object readObject() eingelesen werden. − readObject() gibt als Resultat ein Objekt vom Datentyp Object zurück. Dieses muss nun zuerst wieder in ein Objekt der gewünschten Klasse (Konto) zurückgewandelt werden. − Damit ist die Wiederherstellung des Konto-Objekts abgeschlossen. import ... Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 26 public class BankApplikation extends Frame implements ActionListener, WindowListener { private Konto[] konto = new Konto[20]; ... // speichert alle Kontos des Arrays konto public void kontosSpeichern(String dirname, String filename){ try{ FileOutputStream ostream = new FileOutputStream(dirname + filename); ObjectOutputStream objstream = new ObjectOutputStream(ostream); for (int i= 0; i < konto.length; i++){ objstream.writeObject(konto[i]); } objstream.flush(); ostream.close(); } catch (IOException e){ System.out.println("Error while saving accounts"); e.printStackTrace(); } } // lädt alle Kontos des Arrays konto public void kontosLaden(String filename, String dirname){ try{ FileInputStream instream = new FileInputStream(dirname + filename); ObjectInputStream objstream = new ObjectInputStream(instream); for (int i=0; i<konto.length;i++){ konto[i] = (Konto)objstream.readObject(); } instream.close(); } catch (IOException e){ System.out.println("Error while loading accounts"); e.printStackTrace(); } catch (ClassNotFoundException e){ System.out.println("Error while loading accounts"); e.printStackTrace(); } } } Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 27 Programm 7: BankApplikation − Da beim Serialisieren eines Objekts auch alle damit verbundenen Objekte ebenfalls serialisiert werden, können die Kontos im Progamm Fehler! Verweisquelle konnte nicht gefunden werden. auch einfach abgespeichert werden, indem der Konto-Array konto als Ganzes serialisiert wird. Damit werden automatisch auch alle Konto-Objekte mit serialisiert. Die entsprechenden Methoden sind im Programm Fehler! Verweisquelle konnte nicht gefunden werden. aufgelistet. public class BankApplikation extends Frame implements ActionListener, WindowListener { private Konto[] konto = new Konto[20]; ... // speichert Konto-Array konto public void kontoArraySpeichern(String dirname, String filename){ try{ FileOutputStream ostream = new FileOutputStream(dirname + filename); ObjectOutputStream objstream = new ObjectOutputStream(ostream); objstream.writeObject(konto); objstream.flush(); ostream.close(); } catch (IOException e){ System.out.println("Error while saving accounts"); e.printStackTrace(); } } // lädt Konto-Array konto public void kontoArrayLaden(){ try{ FileInputStream instream = new FileInputStream(dirname + filename); ObjectInputStream objstream = new ObjectInputStream(instream); konto = (Konto[])objstream.readObject(); instream.close(); } catch (IOException e){ System.out.println("Error while loading accounts"); e.printStackTrace(); Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 28 } catch (ClassNotFoundException e){ System.out.println("Error while loading accounts"); e.printStackTrace(); } } Programm 8: Speichern des Konto-Arrays als Ganzem 1.16 Professionelles Programmieren Arbeiten mit Dateien.doc © 2010 InIT/ZHAW 29