Nachts, wenn es dunkel ist ...
- Wollten Sie nicht immer schon wissen, wie es kommt, dass Ihr Computer am nächsten Tag noch so viel weiß, obwohl er nachts ausgeschaltet war?
- Wie macht ein Computer das?
- Ganz ohne Strom!
- Wenn es so bitter kalt ist.
- ???
- Sein Geheimnis: Er benutzt heimlich Dateien!
Datei per Scanner einlesen
Unter java.util findet sich die Klasse Scanner. Scanner ist vielen Java-Programmierern als Möglichkeit bekannt, Daten auf die Schnelle von der Tastatur einzulesen.java.util.Scanner eingabe = new java.util.Scanner(System.in); double zahl = eingabe.nextDouble();Der Scanner liest nicht nur den Standardeingabekanal, sondern auch Dateien.
java.io.File datei = new java.io.File("datei.txt"); if (datei.exists()) { java.util.Scanner eingabe; try { eingabe = new java.util.Scanner(datei); double zahl = eingabe.nextDouble();Scannen Sie wohl! Scanner kennt folgende Methoden:
- scanner.hasNext()
Die Methode prüft, ob es weitere Eingaben gibt. - scanner.nextInt()
Holt nächsten ganzen Zahlenwert - scanner.nextDouble()
Holt den nächsten Fließkommawert. ACHTUNG! Wenn der Computer deutsch eingerichtet ist, wird als Trennzeichen ein Komma und kein Punkt erwartet. - scanner.next()
Holt den nächsten Text. Leerzeichen, Zeilenvorschübe und Tabs sind Trennzeichen zwischen den Texten. - scanner.nextLine()
Liefert nächste Zeile als String. ACHTUNG! Mischen Sie diese Methode nicht mit den anderen next-Methoden!
Wo ist die Datei?
Aber wo befindet sich die Datei datei.txt, die oben im Beispiel verwendet wird? Das Programm sucht die Datei in dem Verzeichnis, in dem es gestartet wurde.Wurde es aus Eclipse gestartet, so schaut es im Wurzelverzeichnis des Projekts nach. Das ist sehr praktisch, weil man dort sehr einfach eine Datei aus der Eclipse-Oberfläche erstellen und dann gleich mit dem Editor bearbeiten kann.
- New | File und Dateiname angeben.
- Die Datei befindet sich im Wurzelverzeichnis des Projekts
Bei Windows kommt noch der lästige Laufwerksbuchstabe hinzu. Abgesehen davon, dass Windows als Pfadtrenner aus den Backslash ausgewichen ist, weil MS-DOS ursprünglich keine Verzeichnisse kannte und den Schrägstrich bereits für Optionen verwendet hatte.
Der Backslash hat den Nachteil, dass er in Strings bereits als Escape-Zeichen verwendet wird. Darum muss für einen Backslash immer \\ eingegeben werden.
Inzwischen kann auch Windows /, Sie können also auch einen / verwenden. Ganz korrekt ist es, wenn Sie File.separator als portables Pfadtrennerzeichen verwenden.
Benutzer suchen lassen
Es gibt Dateiauswahlboxen! Die haben nicht nur den Vorteil, dass der Anwender das Programm für besonders benutzerfreundlich hält, sie liefern sogar das Datei-Handle, ohne dass sich der Programmierer um den Pfad kümmern muss.import javax.swing.JFileChooser; // ... JFileChooser chooser = new JFileChooser(); if (chooser.showOpenDialog(null)==JFileChooser.APPROVE_OPTION) { datei = chooser.getSelectedFile(); } try { java.util.Scanner eingabe = new java.util.Scanner(datei); double zahl = eingabe.nextDouble();
Ausnahmebehandlung
In kaum einem Bereich kann so viel in die Hose gehen, wie im IO-Bereich. Diese Fälle lösen eine Ausnahmesituation aus, die der Programmierer entsprechend behandeln soll. Es treten folgende Exceptions auf:- FileNotFoundException: Das Programm sucht nach einer Datei, die es gar nicht gibt.
- InputMismatchException: Kommt vor allem bei nextInt oder nextDouble von Scanner vor. Der eingelesene Wert passt nicht zu den Erwartungen des Programms.
- NoSuchElementException: Das Programm versucht, weitere Daten zu lesen, aber die Datei ist bereits am Ende.
- Exception
java.io.File datei = new java.io.File("datei.txt"); if (datei.exists()) { java.util.Scanner eingabe = null; try { eingabe = new java.util.Scanner(datei); while(eingabe.hasNext()) { double zahl = eingabe.nextDouble(); System.out.println(zahl); }; } catch (FileNotFoundException e) { System.out.println("Wo ist die Datei?"); } catch (InputMismatchException e) { System.out.println("Das soll eine Zahl sein?"); } catch (NoSuchElementException e) { // fertig gelesen. Da ist nichts mehr! } catch (Exception e) { // Gebe die Exception aus! e.printStackTrace(); } if (eingabe!=null) { eingabe.close(); } }Am Ende wird ein artiger Programmierer den Scanner mit close schließen.
Schreiben von Daten
Wenn man niemanden findet, der die Dateien per Editor eintippt, ist es gut, wenn man ein Programm schreiben kann, dass solche Dateien erzeugt. Wie in vielen wissenschaftlichen Studien üblich, erstellt auch dieses Beispielprogramm seine Ergebnisse auf der Basis eines verlässlichen Zufallsgenerators.import java.io.FileWriter; import java.io.IOException; import java.util.Random; public class Zahlenschreiben { public static void main(String[] args) { Random random = new Random(); String linefeed = System.getProperty("line.separator"); try { FileWriter datei = new FileWriter("datei.txt"); for (int i=0; i<10; i++) { double wert = random.nextDouble(); String strWert = "" + wert; strWert = strWert.replace('.', ','); datei.write(strWert+linefeed); } datei.close(); } catch (IOException e) { } } }Um Dateien zu schreiben, wird ein Objekt der Klasse FileWriter angelegt, das als Parameter den Dateinamen erhält.
Die Klasse stellt eine Methode write zur Verfügung, mit der sich Strings in Dateien schreiben lässt. Auch in diesem Fall sollte die Datei wieder mit close geschlossen werden.
Einschub: Block-Devices
Festplatten sind Block-Devices. Jeder Lese- und Schreibzugriff überträgt mehrere KByte an Daten. Bei kleinen Änderungen gibt es darum unnötigen IO. Da eine Festplatte ca 1000 Mal langsamer als der Hauptspeicher ist, verwendet man zum Schreiben RAM-Puffer. Schreibt Ihr Programm also größere Datenmengen, können Sie es erheblich beschleunigen, indem Sie BufferedWriter verwenden.
import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.util.Random; public class Zahlenschreiben { public static void main(String[] args) { Random random = new Random(); String linefeed = System.getProperty("line.separator"); try { FileWriter fw = new FileWriter("datei.txt"); BufferedWriter datei = new BufferedWriter(fw); for (int i=0; i<10; i++) { double wert = random.nextDouble(); String strWert = "" + wert; strWert = strWert.replace('.', ','); datei.write(strWert+linefeed); } datei.close(); } catch (IOException e) { } } }Das Programm unterscheidet sich minimal von dem vorigen. Es wird lediglich ein Objekt der Klasse BufferedWriter auf der Basis des FileWriters erzeugt und dieses mit dem Schreiben in die Datei beauftragt.
FileReader und BufferedReader
Für das Lesen von Textdateien wurde oben bereits der Scanner vorgestellt. Es gibt aber auch ein Gegenstück zum FileWriter namens FileReader.FileReader stellt die Methode read zur Verfügung, die aber nur ein Zeichen liefert. Analog zum BufferedWriter gibt es zum Lesen die Klasse BufferedReader, die die Methode readLine zur Verfügung stellt, die eine ganze Zeile liefert.
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class DateiBufferedLesen { public static void main(String[] args) { BufferedReader datei; String zeile; try { datei = new BufferedReader( new FileReader("datei.txt")); while ((zeile = datei.readLine()) != null) { System.out.println(zeile); } datei.close(); } catch (IOException e) { System.err.println("Dateilesefehler"); } } }
Klassen in die Datei
Natürlich lassen sich alle möglichen Typen in Text und auch wieder zurück wandeln. Sollen aber Objekte persistent gehalten werden, wird der Aufwand schnell groß. Aber das muss nicht sein. Java kann helfen.
Klasse implementiert Serializable
Die zu sichernde Klasse muss serialisierbar sein. Um das zu gewährleisten, genügt es, der Klasse beizufügen, dass sie das Interface Serializable implementiert. Eine Implementierung von Methoden erzwingt das nicht.import java.io.Serializable; public class KlassenIO { static class Auto implements Serializable { String marke, modell; int hubraum, ps; double preis; } public static void main(String[] args) { Auto auto = new Auto(); auto.marke = "Rollsreuss"; auto.modell = "R4"; auto.hubraum = 845; auto.ps = 280; auto.preis = 12879;
Schreiben des Objekts
Für einen FileOutputStream wird ein ObjectOutputStream angelegt. Dieser stellt die Methode writeObject zur Verfügung. Diese Methode sichert jedes serialisierbare Objekt.static void schreibeAuto(Auto auto) throws IOException { FileOutputStream fos = new FileOutputStream("auto.data"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(auto); oos.close(); }
Objekt wieder einlesen
Um ein Objekt zu lesen, wird der Vorgang umgekehrt. Ein ObjectInputStream wird aus einem FileInputStream erzeugt. Die Methode readObject liefert ein serialisierbares Objekt, das vor der weiteren Verwendung gecastet werden muss.static Auto leseAuto() throws IOException { Auto auto = null; FileInputStream fis = new FileInputStream("auto.data"); ObjectInputStream ois = new ObjectInputStream(fis); try { auto = (Auto) ois.readObject(); } catch (ClassNotFoundException e) { } ois.close(); return auto; }
Hauptprogramm
Da die Methoden IOException werfen, muss main sie fangen.public static void main(String[] args) { Auto auto = new Auto(); auto.marke = "Rollsreuss"; auto.modell = "R4"; auto.hubraum = 845; auto.ps = 280; auto.preis = 12879; try { schreibeAuto(auto); Auto wagen = leseAuto(); if (wagen!=null) { System.out.println(wagen.marke); } } catch (IOException e) { } }
Betrachtung der Dateiformate
Scanner, FileWriter und FileReader verarbeiten reine Textdateien.FileInputStream und FileOutputStream verwenden spezielle Java-Dateien, um darin die Referenzen und binäre Daten zu kodieren.
Portable Dateiformate sind Textdateien, die von mehreren Programmiersprachen und Plattformen interpretierbar sind, wie XML oder JSON.
Wahlfreier Zugriff (Random Access)
Die bisherigen Dateizugriffe erfolgen sequentiell. Die Klasse RandomAccessFile ermöglicht das Positionieren an eine beliebige Position einer Datei und das Auslesen eines Blocks.- seek: Positionieren an eine bestimme Stelle der Datei
- read: Block lesen
- write: Block schreiben
Man könnte diese Behandlung auch als "Array als Datei" betrachten. Solche wahlfreien Zugriffe sind die Basis einer Datenbank und können beispielsweise verwendet werden, um fremde Dateiformate zu lesen.
final int BLOCKSIZE = 1024; final int OFFSET = 0; int pos = 0; byte[] block = new byte[BLOCKSIZE]; try { RandomAccessFile datei = new RandomAccessFile("test.dbf", "rw"); datei.seek(pos * BLOCKSIZE + OFFSET); datei.write(block); pos++; datei.seek(pos * BLOCKSIZE + OFFSET); datei.read(block); datei.close(); } catch(FileNotFoundException e) { } catch(IOException e) { }