W3docs

Dateien in Java lesen

Textdateien und Binärdateien in Java lesen mit FileReader, BufferedReader, Scanner, Files.readString und Streams.

Es gibt fünf gängige Methoden, um eine Textdatei in Java zu lesen. Die richtige Wahl hängt fast ausschließlich von der Dateigröße und dem gewünschten Umgang mit dem Inhalt ab. Dieses Kapitel stellt die fünf Methoden vor – von der einfachsten bis zur flexibelsten:

  1. Files.readString(path) — ganze Datei als ein String.
  2. Files.readAllLines(path) — ganze Datei als List<String>.
  3. Files.readAllBytes(path) — ganze Datei als byte[].
  4. Files.lines(path) — Datei als Stream<String>, lazy.
  5. BufferedReader / Scanner — klassische Dekoratoren, volle Kontrolle.

Verwenden Sie das kleinstmögliche Werkzeug, das passt. Eine 4 GB große Log-Datei mit Files.readString zu lesen führt zu einem OutOfMemoryError; eine 12-zeilige Konfigurationsdatei mit BufferedReader und einer while-Schleife zu lesen sind sechs Zeilen Code, obwohl eine ausreichen würde.

Files.readString(path) — ganze Datei, ein Aufruf

String text = Files.readString(Path.of("config.json"), StandardCharsets.UTF_8);

Hinzugefügt in Java 11. Gibt die vollständige Datei als String zurück. Verwendet seit Java 18 standardmäßig UTF-8 (ein explizites Angeben des Charset wird dennoch dringend empfohlen, auch mit dem neuen Standard). Wirft IOException, wenn die Datei nicht existiert oder nicht gelesen werden kann; wirft OutOfMemoryError, wenn die Datei größer als der Heap ist.

Verwendung: wenn die Datei „klein genug" ist — Konfigurationsdateien, JSON-Payloads, MDX-Kapitel, alles, was man in einem einzigen Editor-Fenster lesen würde. Die klassische informelle Regel ist unter einigen Megabyte.

Files.readAllLines(path) — Liste von Zeilen

List<String> lines = Files.readAllLines(Path.of("hosts.txt"), StandardCharsets.UTF_8);

Gibt eine unveränderliche List<String> der Zeilen der Datei zurück, ohne Zeilenabschlusszeichen. Das Speicherprofil entspricht dem von readString zuzüglich des List-Overheads — auch hier wird die gesamte Datei im Speicher gehalten.

Verwendung: wenn Sie nach Zeilennummer indizieren, die Datei sortieren oder Zeilen in eine for (String line : lines)-Schleife einlesen möchten, ohne Streams einrichten zu müssen.

Files.readAllBytes(path) — Rohe Bytes

byte[] raw = Files.readAllBytes(Path.of("photo.png"));

Das Byte-Äquivalent. Kein Charset, da keine Dekodierung stattfindet. Verwenden Sie diese Methode für Binärdateien (Bilder, Archive, ausführbare Dateien) oder wenn Sie einen Hash berechnen oder Bytes in einen ByteArrayInputStream leiten müssen.

Files.lines(path) — Lazy Stream

try (Stream<String> lines = Files.lines(Path.of("app.log"), StandardCharsets.UTF_8)) {
  long errors = lines.filter(l -> l.contains("ERROR")).count();
}

Dies ist der einzige eingebaute Reader, der beliebig große Dateien verarbeiten kann. Der Stream<String> ist lazy — Zeilen werden bei Bedarf gelesen, nicht alle auf einmal — und fügt sich direkt in das Stream-Pipeline-Vokabular ein (filter, map, count, toList).

Zwei unverzichtbare Regeln:

  • try-with-resources ist erforderlich. Der Stream besitzt ein offenes Datei-Handle; ohne try-with-resources bleibt die Datei bis zum GC offen, und auf einem stark ausgelasteten Server erschöpfen sich die Dateideskriptoren.
  • Verwenden Sie den Stream nicht nach einer terminalen Operation erneut. Streams sind für den einmaligen Gebrauch.

Verwendung: wenn die Datei für readAllLines zu groß ist oder wenn die zeilenweise Transformation mit dem Rest Ihrer Stream-Pipeline kombiniert werden soll.

BufferedReader.readLine() — der Klassiker

BufferedReader ist das Arbeitstier, das die modernen Hilfsmethoden intern verwenden. Es puffert die zugrunde liegenden Lesevorgänge in einem festgroßen In-Memory-Chunk, sodass readLine() nicht für jedes Zeichen einen Systemaufruf auslöst.

try (BufferedReader in = Files.newBufferedReader(Path.of("hosts.txt"), StandardCharsets.UTF_8)) {
  String line;
  while ((line = in.readLine()) != null) {
    System.out.println(line);
  }
}

Files.newBufferedReader(path) ist die moderne Factory-Methode; die klassische Variante ist new BufferedReader(new FileReader("hosts.txt")) (die auf JDKs älter als 18 den Plattform-Zeichensatz verwendet — mit der dreiargumentigen Überladung auf UTF-8 festlegen). Der Vertrag von readLine() lautet:

  • Gibt die nächste Zeile ohne Zeilenabschlusszeichen zurück (\n, \r oder \r\n).
  • Gibt null am Dateiende zurück. Die Schleifenbedingung (line = readLine()) != null ist das etablierte Idiom.

BufferedReader ist auch ein Stream<String>-Erzeuger: reader.lines() gibt einen Stream<String> zurück, der durch den Reader gespeist wird. So ist Files.lines intern implementiert.

Scanner — Token-für-Token-Parsing

Scanner liest Text nach Tokens — Wörter, Integer, Doubles, Zeilen oder sogar Regex-Treffer — und ist das richtige Werkzeug zum Lesen strukturierter Eingaben, bei denen die Einheiten keine ganzen Zeilen sind.

try (Scanner sc = new Scanner(Files.newBufferedReader(Path.of("nums.txt")))) {
  while (sc.hasNextInt()) {
    int n = sc.nextInt();
    System.out.println(n * n);
  }
}

Scanner ist langsamer als BufferedReader, weil er parst; er alloziert kurze Strings und führt Regex aus. Für zeilenweise Verarbeitung bevorzugen Sie BufferedReader. Für typisierte Tokens aus einer kleinen Datei (Zahlen, Wörter, CSV-ähnliche Eingaben) erspart Scanner die Parsing-Ebene.

Es gibt ein vollständiges Kapitel über Scanner weiter hinten in diesem Teil — dies ist die Variante zum Lesen einer Datei.

FileReader — der rohe Zeichen-Reader

try (FileReader in = new FileReader("notes.txt", StandardCharsets.UTF_8)) {
  int c;
  while ((c = in.read()) != -1) {
    System.out.print((char) c);
  }
}

FileReader liest Zeichen direkt aus der Datei — ohne Pufferung, ohne Zeilenerkennung, ohne automatische Dekodierungseinstellungen (Sie übergeben das Charset oder akzeptieren den Plattformstandard auf JDKs älter als 18). Es ist die Schicht, auf der die anderen aufbauen. Im Anwendungscode verwenden Sie ihn fast nie direkt; Sie kapseln ihn in einem BufferedReader.

Er ist dennoch nützlich, wenn Sie einige hundert Zeichen lesen und dann stoppen möchten — kleine Nachschlagevorgänge, bei denen die Kosten für die Buffer-Einrichtung durch die Aufrufkosten in den Schatten gestellt werden.

Welche Methode verwenden

SzenarioWahl
Kleine Datei als einzelner StringFiles.readString
Kleine Datei als List<String>Files.readAllLines
Binärdatei (Bild, Archiv)Files.readAllBytes
Beliebige Datei mit Stream-TransformationFiles.lines (innerhalb von try-with-resources)
Zeilenweise Schleife, volle KontrolleFiles.newBufferedReader + readLine
Typisierte Tokens (Ints, Wörter, Regex-Treffer)Scanner
Zeichen für Zeichen, kleine DateiFileReader

Die richtige Standardwahl für den Fall „Ich möchte einfach diese kleine Textdatei laden" ist Files.readString. Die richtige Standardwahl für „Diese riesige Log-Datei verarbeiten, ohne den Speicher zu sprengen" ist Files.lines.

Ein ausgearbeitetes Beispiel: dieselbe Datei, fünf Reader

Das folgende Programm schreibt eine kleine Textdatei und liest sie dann auf fünf verschiedene Arten — readString, readAllLines, Files.lines gefiltert durch ein Predicate<String> aus dem Vokabular von Teil 12, BufferedReader.readLine und Scanner für tokenisierte Integer. Jeder Block gibt aus, was er gelesen hat, damit Sie die Formen nebeneinander sehen können.

java— editable, runs on the server

Was man aus dem Ergebnis mitnehmen kann:

  • Files.readString hat die gesamte Datei als einen String zurückgegeben — einfach und genau das, was man für kleine Konfigurationen und Vorlagen möchte. Bei einer 4 GB großen Log-Datei hätte es einen OutOfMemoryError ausgelöst.
  • Files.readAllLines hat eine indizierbare List<String> zurückgegeben, bei der die Zeilenabschlusszeichen entfernt sind. lines.get(0) hat funktioniert, weil die Liste im Speicher materialisiert ist; mit einem Stream wäre das nicht möglich.
  • Files.lines(file) wurde innerhalb von try-with-resources geöffnet, weil der Stream das Datei-Handle besitzt. Die Pipeline .filter(isError).count() hat dieselbe Form wie alles aus Teil 12 — nur die Quelle hat sich geändert.
  • BufferedReader.readLine() hat null am Dateiende zurückgegeben. Die for-Schleife hat hier absichtlich nach drei Zeilen abgebrochen, aber das idiomatische Produktionsmuster lautet while ((line = in.readLine()) != null).
  • Scanner hat Zeilen übersprungen, die nicht mit einem Integer begannen, und dann Tokens mit nextInt() gelesen, bis keine mehr vorhanden waren. Derselbe Scanner hätte auch Doubles (nextDouble), Regex-Treffer (findInLine) oder BigIntegers lesen können — das ist der Grund, warum er pro Token mehr kostet als BufferedReader pro Zeile.

Was kommt als nächstes

Das nächste Kapitel, Dateien in Java schreiben, behandelt die Schreibseite derselben APIs — Files.writeString, Files.write, BufferedWriter, PrintWriter und die StandardOpenOption-Flags (APPEND, CREATE_NEW, TRUNCATE_EXISTING), die bestimmen, wie eine vorhandene Datei behandelt wird.

Übungen

Übung
Sie müssen eine 5 GB große Server-Log-Datei zeilenweise verarbeiten und zählen, wie viele Zeilen das Wort `ERROR` enthalten. Welcher Reader ist die richtige Wahl?
Sie müssen eine 5 GB große Server-Log-Datei zeilenweise verarbeiten und zählen, wie viele Zeilen das Wort `ERROR` enthalten. Welcher Reader ist die richtige Wahl?
Was this page helpful?