Come leggere un file riga per riga in Java
Leggi un file Java riga per riga con BufferedReader, Files.lines, Files.readAllLines e Scanner.
Leggere un file di testo una riga alla volta è una delle operazioni su file più comuni in Java. Il JDK offre diversi modi per farlo; la scelta giusta dipende dalla dimensione del file e dal fatto che si preferisca un semplice ciclo o una pipeline a stream. Questo capitolo mostra i quattro approcci idiomatici e quando usare ciascuno.
BufferedReader: il cavallo di battaglia per lo streaming
BufferedReader.readLine() legge una singola riga per chiamata e restituisce null alla fine del file, quindi si abbina naturalmente a un ciclo while. Racchiudilo in try-with-resources così il reader sottostante si chiude da solo:
Path file = Path.of("notes.txt");
try (BufferedReader br = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}Questo legge il file in streaming: solo una riga è tenuta in memoria per volta, quindi gestisce log di diversi gigabyte senza problemi. Files.newBufferedReader usa UTF-8 per impostazione predefinita, ma passare esplicitamente il charset documenta l'intenzione ed evita la decodifica dipendente dalla piattaforma. Nota che readLine() rimuove il terminatore di riga (\n, \r, o \r\n), quindi non lo vedrai mai nella stringa restituita.
La forma try (...) sopra è try-with-resources: garantisce la chiusura del reader anche se il ciclo lancia un'eccezione. Vedi stream bufferizzati per capire perché racchiudere un reader grezzo in un BufferedReader è importante per le prestazioni.
Files.lines: lo stesso lavoro come Stream
Quando vuoi una pipeline funzionale — filter, map, count — Files.lines ti fornisce uno Stream<String> pigro sulle righe del file:
try (Stream<String> lines = Files.lines(Path.of("notes.txt"), StandardCharsets.UTF_8)) {
lines.filter(s -> !s.isBlank())
.map(String::trim)
.forEach(System.out::println);
}Come BufferedReader, legge in modo pigro e non carica mai l'intero file. Il problema è che lo stream mantiene un file handle aperto, quindi deve essere chiuso — usalo sempre dentro try-with-resources, mai come espressione nuda. Dimenticarsi di farlo provoca una perdita di descrittori.
Files.readAllLines: file piccoli, tutto in una volta
Se il file è piccolo e vuoi tutte le righe in una List<String> fin dall'inizio, Files.readAllLines è l'opzione più diretta:
List<String> all = Files.readAllLines(Path.of("notes.txt"), StandardCharsets.UTF_8);
for (String line : all) {
System.out.println(line);
}È eagerly: l'intero file viene decodificato in memoria prima che tu tocchi la prima riga. Questo è comodo per file di configurazione e fixture, ma non adatto a file grandi — per quelli preferisci gli approcci in streaming. Scanner con nextLine() e hasNextLine() è una quarta opzione, utile quando devi anche analizzare token, ma è più lento e facile da usare in modo errato, quindi i tre sopra coprono la maggior parte dei casi. Per una visione più ampia dell'I/O su file — apertura, lettura di file interi e l'API NIO Files — vedi leggere file in Java.
| Approccio | Memoria | Restituisce | Ideale per |
|---|---|---|---|
BufferedReader.readLine() | Una riga | Ciclo semplice | File grandi, controllo manuale |
Files.lines() | Una riga (pigro) | Stream<String> | Pipeline su file grandi |
Files.readAllLines() | Intero file | List<String> | File piccoli, accesso casuale |
Scanner.nextLine() | Una riga | Ciclo semplice | Parsing misto di righe e token |
Esempio pratico: tutti e tre a confronto
Questo programma scrive un piccolo file temporaneo (così è autonomo), poi lo legge in tre modi — un ciclo con BufferedReader, uno stream con Files.lines, e un Files.readAllLines eager:
Cosa imparare dall'esecuzione:
- Il ciclo con
BufferedReadernumera quattro righe, e la riga3: []mostra che una riga vuota nel file viene restituita come stringa vuota, non saltata —readLine()riporta ogni riga, incluse quelle vuote. readLine()ha stampato ogni riga senza alcun\nfinale, confermando che rimuove il terminatore di riga; le uniche parentesi intorno al testo sono le lettere[e]aggiunte dal codice.Files.linesha contatonon-blank lines: 3perchéfilter(s -> !s.isBlank())ha scartato la riga vuota — la pipeline a stream opera in modo pigro sulle stesse quattro righe viste dal ciclo.Files.readAllLinesha riportatototal lines: 4efirst line : alpha, dimostrando che ha caricato l'intero file eagerly in unaList<String>indicizzabile conget(0).- Ogni reader si trovava dentro try-with-resources (o restituiva una
Listgestita), quindi il file handle e il file temporaneo sono stati rilasciati correttamente prima che venisse stampatodone— nessun descrittore perso.