Helmichs Informatik-Lexikon

Textdateien einlesen

Betrachten Sie folgenden Java-Code:

public void show(String dateiname) throws IOException
{
   FileReader reader = new FileReader(dateiname);
   BufferedReader inBuffer = new BufferedReader(reader);

   String line = inBuffer.readLine();
   
   while (line != null)
   {
      System.out.println(line);
      line = inBuffer.readLine();
   } 
}

Diese show-Methode öffnet eine Textdatei mit dem übergebenem Dateinamen und gibt sie Zeile für Zeile in der Konsole aus.

Um diesen Code auszuprobieren, bauen wir ihn in eine Klasse Lesenein. Der Konstruktor dieser Klasse müsste ungefähr so aussehen:

public Lesen()
{
   try
   {
      show("Test.txt");
   }
   catch(IOException e)
   {
      System.out.println("Fehler beim Öffnen der Datei");
   }
}

Die Methode show wird versuchsweise aufgerufen mit der try-Anweisung. Wenn es dabei zu keinem Laufzeitfehler kommt, ist alles gut. Der show-Befehl wird dann ausgeführt. Kommt es aber zu einem Laufzeitfehler, stürzt das Programm nicht einfach ab, sondern es wird eine geordnete Fehlermeldung (IOException) ausgegeben, nämlich "Fehler beim Öffnen der Datei".

Einzelheiten

Betrachten wir den Quelltext der Methode show etwas näher. Beginnen wir mit der ersten Zeile, dem Methoden-Kopf oder der Signatur:

public void show(String dateiname) throws IOException

Dass wir der Methode den Namen der zu ladenden Datei als Parameter übergeben, ist selbstverständlich. Nicht so ganz leicht zu durchschauen ist schon der zweite Teil des Kopfes: throws IOException.

Mit diesem Zusatz teilen wir dem Java-System mit, dass diese Methode eine eigene Fehlermeldung ausgeben kann. Wie diese Fehlermeldung dann weiterverwertet wird, hängt ganz vom Betriebssystem ab. Es könnte zum Beispiel eine einfache MessageBox mit einer entsprechenden Fehlermeldung erscheinen, oder das Programm stürzt plötzlich ab und das Betriebssystem zeigt den Java-Editor mit einer als fehlerhaft markierten Zeile und einem Hinweis, um nur zwei mögliche Beispiele zu nennen.

In der nächsten Zeile

FileReader reader = new FileReader(dateiname);

wird ein Objekt reader der Klasse FileReader angelegt. Dem Konstruktor wird dabei der Dateiname der zu öffnenden Textdatei übergeben. In der Java-Dokumentation wird die Klasse FileReader als "Convenience class for reading character files" bezeichnet. Man könnte also auch auf einem anderen Weg auf Textdateien zugreifen, aber FileReader vereinfacht den Zugriff erheblich.

Jetzt wird es noch komplizierter:

BufferedReader inBuffer = new BufferedReader(reader);

Ein neues Objekt inBuffer der Klasse BufferedReader wird angelegt und der Konstruktor bekommt dabei das eben erzeugten FileReader-Objekt reader als Parameter. Um den Sinn der Klasse BufferedReader zu verstehen, werfen wir einen Blick in die Java-Dokumentation:

In general, each read request made of a Reader causes a corresponding read request to be made of the underlying character or byte stream. It is therefore advisable to wrap a BufferedReader around any Reader whose read() operations may be costly, such as FileReaders and InputStreamReaders. For example,

BufferedReader in = new BufferedReader(new FileReader("foo.in"));

will buffer the input from the specified file. Without buffering, each invocation of read() or readLine() could cause bytes to be read from the file, converted into characters, and then returned, which can be very inefficient.

Wie ist dies zu verstehen? Würde man direkt über die Klasse FileReader auf die Textdatei zugreifen, so würde jede Leseoperation einen rechenintensiven Zugriff auf die Textdatei verursachen. Würden zwei oder drei Zeilen nacheinander eingelesen, müsste zwei oder dreimal auf die Textdatei zugegriffen werden. Zugriffe auf Textdateien oder auf Dateien überhaupt sind recht zeitaufwändig. Stark vereinfacht kann man sich vorstellen, dass ja erst mal die "richtige Stelle auf der Festplatte bzw. im Arbeitsspeicher" gefunden werden muss, bevor auch nur ein Byte aus einer Datei gelesen werden kann.

Die Klasse BufferedReader bewirkt nun aber einen gepufferten Zugriff auf die Textdatei. Wenn die Textdatei geöffnet wird, wird nicht nur eine Zeile gelesen, sondern mehrere auf einmal. Die im Augenblick nicht benötigten Zeilen werden in dem Buffer gespeichert. Folgt dann ein erneuter Lesezugriff, so wird nicht mehr aus der Textdatei direkt gelesen, sondern aus dem Buffer, was wesentlich schneller geht.

Kommen wir nun zur nächsten Zeile

String line = inBuffer.readLine();

Die Klasse BufferedReader stellt Methoden für den bequemen Zugriff auf Textdateien zur Verfügung, eine dieser Methoden ist readLine, die, wie der Name schon andeutet, eine Zeile aus dem Buffer zurück liefert. Wird die Methode zum ersten Mal aufgerufen, wird die erste Zeile zurückgeliefert. Beim zweiten Aufruf die zweite Zeile und so weiter. Also eine ganz komfortable Sache.

Nachdem die erste Zeile gelesen und in der Variablen line gespeichert wurde, geht es mit einer while-Schleife weiter:

while (line != null)
{
   System.out.println(line); 
   line = inBuffer.readLine();
}

Die Textdatei bzw. der Buffer endet grundsätzlich mit einem null-Zeichen. Solange der Output des Buffers aber nicht null null, scheint es sich um eine normale Text-Zeile zu handeln, die ohne Probleme weiterverwendet werden kann. In der while-Schleife unseres Beispiels wird diese Zeile zunächst in die Konsole umgeleitet. Anschließend wird die nächste Zeile aus dem Buffer gelesen. Beim nächsten Schleifendurchgang wird wieder geprüft, ob das Dateiende erreicht ist. Falls das der Fall ist, macht es keinen Sinn, die null auszugeben und eine neue Zeile zu lesen, daher bricht die while-Schleife ab.