10.5.4 | RandomAccessFile |
RandomAccessFile ist eine Klasse, mit der wesentlich flexibler auf Dateien zugegriffen werden kann als mit File. Sie bietet dem Benutzer die Möglichkeit, Durch die Festlegung des Zugriffs kann man Fehler vermeiden, da z. B. nicht in eine Datei geschrieben werden kann, die nur mit Leserechten geöffnet wurde.
Die Verwendung von RandomAccessFile soll an einem kleinen Beispiel verdeutlicht werden. Es ist ein Programm, das jedesmal, wenn es aufgerufen wird, einen Spruch auf die Standardausgabe ausgibt (vergleichbar mit dem auf UNIX-Plattformen bekannten Programm fortune). Der auszugebende Spruch soll durch einen Zufallsgenerator bestimmt werden, die Anzahl der Sprüche soll variabel sein:public class RandomAccessDemo { public static void main(String args[]) throws IOException { PrintWriter stdout = new PrintWriter(System.out, true); // Erzeugen des RandomAccessFiles RandomAccessFile datafile = new RandomAccessFile("sayings.dat","r"); // Zufallsposition in der Datei bestimmten long pos = (long)(datafile.length()*Math.random()); // Dateizeiger an die Zufallsposition setzen datafile.seek(pos); // So lange den Dateizeiger rückwärts bewegen, // bis ein Trennzeichen // oder der Anfang der Datei erreicht wird int key = datafile.read(); // Zeichenpuffer while((pos != 0) && ((char)key != '*')) datafile.seek(--pos); // Lesen eines weiteren Zeichens, wenn // nicht der Anfang erreicht wurde if (pos != 0) key = datafile.read(); // Ausgabe einer Leerzeile, // Wenn der erste Spruch gesucht ist if (pos == 0) stdout.println(); // So viele Zeichen ausgeben, bis das Dateiende, oder // der Beginn des nächsten Spruches erreicht wird while(((key = datafile.read()) != (int)'*') && (key != -1)) stdout.print((char)key); stdout.println(); datafile.close(); // Datei schließen } }Wie man sehen kann, ist die Realisierung dieser Aufgabe nicht sehr umfangreich. Auch bei der Klasse RandomAccessFile läßt es sich nicht vermeiden, ein Exemplar zu erzeugen:RandomAccessFile datafile = new RandomAccessFile("sayings.dat","r");Im Unterschied zu File muss jedoch zusätzlich zum Dateinamen der Zugriffsmodus angegeben werden. Der Zugriffsmodus kann entweder nur lesend (r), nur schreibend (w) oder lesend und schreibend (rw) sein. Der Versuch, in eine als nur lesend geöffnete Datei zu schreiben, löst eine IOException aus.
Da ein Spruch aus der Datei ausgelesen werden soll, wird als Zugriffsmodus r übergeben. Als nächstes muss man den Spruch bestimmen, der ausgegeben werden soll. Hierzu sollte man wissen, wie die Sprüche in der Datei verwaltet werden.
Es wird angenommen, dass die Sprüche sequenziell in der Datei angeordnet sind. Zwischen den einzelnen Sprüchen soll ein Stern (*) als Trennzeichen stehen:Dies ist der erste Text in der Beispieldatei * Wenn dieser Text erscheint, dann hat der Zufallsgenerator die Textnummer zwei ausgewählt * Dieser Text steht in der Datei an dritter StelleEs wird nach folgendem Prinzip vorgegangen: Zuerst wird an eine zufällige Stelle innerhalb der Datei gesprungen. Befindet sich diese Stelle mitten in einem Spruch, wird der Dateizeiger so lange um ein Zeichen zurückgesetzt, bis entweder ein Stern oder der Anfang der Datei erreicht ist. Somit ist die Position eines Spruchanfangs gefunden.
Danach werden so lange aufeinanderfolgende Zeichen ausgegeben, bis entweder wieder ein Stern das Ende des Spruches signalisiert oder das Ende der Datei erreicht wurde.
Nun wird der Programmcode analysiert:long pos = (long)(datafile.length()*Math.random()); // Dateizeiger an die Zufallsposition setzen datafile.seek(pos);Um an eine bestimmte Stelle innerhalb der Datei zu springen, besitzt RandomAccessFile die Methode seek(long). Der übergebene Parameter muss allerdings auch innerhalb des gültigen Bereiches liegen. Um dies zu gewährleisten, wird vorher die Länge der Datei mit file.length() abgefragt und mit einer Zufallszahl zwischen 0 und 1 multipliziert (das Ergebnis von Math.random()). Das Pendant zu seek() stellt die Methode getFilePointer() dar. Sie liefert die aktuelle Position des Dateizeigers als Ergebnis. getFilePointer() wird in diesem Beispiel allerdings nicht benutzt. Von der zufällig bestimmten Position aus wird die Datei rückwärts zeichenweise ausgelesen, bis der Dateizeiger bei einem Stern oder dem Dateianfang angekommen ist:int key = datafile.read(); // Zeichenpuffer while((pos != 0) && ((char)key != '*')) datafile.seek(--pos); // Lesen eines weiteren Zeichens, wenn // nicht der Anfang erreicht wurde if (pos != 0) key = datafile.read();Die Ausgabe erfolgt analog dazu:while(((key = datafile.read()) != (int)'*') && (key != -1)) stdout.print((char)key); stdout.println(); datafile.close(); // Datei schließenIst die Ausgabe beendet, wird die Datei wieder geschlossen. Bei der Auswahl des ersten Spruchs muss man zwei Dinge berücksichtigen:Ein weiterer Unterschied der im Vergleich zu File auffällt, ist folgender: Mit RandomAccessFile muss man nicht explizit einen Stream öffnen, um auf die Datei zugreifen zu können. Dies wird automatisch beim Erzeugen des Exemplars getan. So kann direkt über die Methoden von RandomAccessFile in die Datei geschrieben werden.
- Beim Suchen des Spruchanfangs darf kein Zeichen mehr eingelesen werden, wenn sich der Dateizeiger am Dateianfang befindet, da das erste Zeichen der Datei bereits zum ersten Spruch gehört.
- Um alle Sprüche im gleichen Format darzustellen, muss man eine leere Zeile ausgeben. Das liegt daran, dass beim Suchen des Spruches stets bis zum Trennungszeichen zurückgegangen wird. Alle folgenden Zeichen werden ausgegeben: Da nach dem Trennungszeichen in der Datei eine neue Zeile begonnen wird, folgt zuerst ein Newline-Zeichen. Am Anfang der Datei befindet sich jedoch kein solches Newline-Zeichen.
RandomAccessFile-Exemplare können nicht nur direkt über den Dateinamen angelegt werden, sondern auch über bereits bestehende File-Exemplare. Hierzu wird ein zweiter Konstruktor definiert:File f = new File("sayings.dat"); RandomAccessFile rand = new RandomAccessFile(f, "r");Die Klasse RandomAccessFile definiert verschiedene Methoden, die sowohl lesenden als auch schreibenden Zugriff auf die Datei ermöglichen. Ähnlich wie bei den Data-Streams wird auch die Ein- und Ausgabe für unterschiedliche Datentypen unterstützt.
Es sollte aber beachtet werden, dass die Klasse RandomAccessFile direkt keine Methode zur Verfügung stellt, die ein Einlesen eines Strings unter Berücksichtigung einer bestimmten Zeichenkodierung, durchführt. RandomAccessFile definiert lediglich die Methode readLine(), die ab der aktuellen Dateiposition eine Zeile einliest und sie zurückliefert. Diese Methode arbeitet aber nur bei einer Kodierung in iso-latin-1 korrekt, da das höherwertige Byte bei der Konvertierung in einen Character immer den Wert 0 erhält.
Die Berücksichtigung der Zeichenkonvertierung kann durch einen Umweg bewerkstelligt werden: Zunächst werden die Daten mit der read(byte[], int, int)-Methode in ein Byte-Array eingelesen. Die Daten in diesem Array können anschließend mit einem Exemplar von ByteToCharConverter in Characters unter Berücksichtigung einer bestimmten Kodierung umgewandelt werden. Aus den einzelnen Characters kann man nun einen String erzeugen.