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:
Files.readString(path)— ganze Datei als einString.Files.readAllLines(path)— ganze Datei alsList<String>.Files.readAllBytes(path)— ganze Datei alsbyte[].Files.lines(path)— Datei alsStream<String>, lazy.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; ohnetry-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,\roder\r\n). - Gibt
nullam Dateiende zurück. Die Schleifenbedingung(line = readLine()) != nullist 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
| Szenario | Wahl |
|---|---|
Kleine Datei als einzelner String | Files.readString |
Kleine Datei als List<String> | Files.readAllLines |
| Binärdatei (Bild, Archiv) | Files.readAllBytes |
| Beliebige Datei mit Stream-Transformation | Files.lines (innerhalb von try-with-resources) |
| Zeilenweise Schleife, volle Kontrolle | Files.newBufferedReader + readLine |
| Typisierte Tokens (Ints, Wörter, Regex-Treffer) | Scanner |
| Zeichen für Zeichen, kleine Datei | FileReader |
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.
Was man aus dem Ergebnis mitnehmen kann:
Files.readStringhat die gesamte Datei als einenStringzurü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 einenOutOfMemoryErrorausgelöst.Files.readAllLineshat eine indizierbareList<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 vontry-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()hatnullam Dateiende zurückgegeben. Diefor-Schleife hat hier absichtlich nach drei Zeilen abgebrochen, aber das idiomatische Produktionsmuster lautetwhile ((line = in.readLine()) != null).Scannerhat Zeilen übersprungen, die nicht mit einem Integer begannen, und dann Tokens mitnextInt()gelesen, bis keine mehr vorhanden waren. DerselbeScannerhätte auch Doubles (nextDouble), Regex-Treffer (findInLine) oderBigIntegers lesen können — das ist der Grund, warum er pro Token mehr kostet alsBufferedReaderpro 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.