Leggere File in Java
Leggi file di testo e binari in Java con FileReader, BufferedReader, Scanner, Files.readString e stream.
Esistono cinque modi comuni per leggere un file di testo in Java, e la scelta giusta dipende quasi interamente dalla dimensione del file e da cosa si vuole fare con il contenuto. Questo capitolo illustra i cinque metodi dal più semplice al più flessibile:
Files.readString(path)— l'intero file come un'unicaString.Files.readAllLines(path)— l'intero file come unaList<String>.Files.readAllBytes(path)— l'intero file come unbyte[].Files.lines(path)— il file come unoStream<String>, lazy.BufferedReader/Scanner— decoratori classici, controllo completo.
Scegli lo strumento più piccolo che si adatta alle tue esigenze. Leggere un log da 4 GB con Files.readString causa un OutOfMemoryError; leggere una configurazione di 12 righe con BufferedReader e un ciclo while sono sei righe di codice dove ne basterebbe una.
Files.readString(path) — intero file, una chiamata
String text = Files.readString(Path.of("config.json"), StandardCharsets.UTF_8);Aggiunto in Java 11. Restituisce l'intero file come String. Usa UTF-8 per default da Java 18 (Charset è comunque fortemente consigliato per specificarlo esplicitamente, anche con il nuovo default). Lancia IOException se il file non esiste o non può essere letto; lancia OutOfMemoryError se il file è più grande dell'heap.
Da usare quando: il file è "abbastanza piccolo" — file di configurazione, payload JSON, capitoli MDX, qualsiasi cosa che saresti disposto a leggere in una singola finestra di editor. La regola informale classica è meno di qualche megabyte.
Files.readAllLines(path) — lista di righe
List<String> lines = Files.readAllLines(Path.of("hosts.txt"), StandardCharsets.UTF_8);Restituisce una List<String> immutabile delle righe del file, con i terminatori di riga rimossi. Ha lo stesso profilo di memoria di readString più l'overhead della List — mantiene anch'esso l'intero file in memoria.
Da usare quando: vuoi indicizzare per numero di riga, ordinare il file, o passare le righe a un ciclo for (String line : lines) senza configurare stream.
Files.readAllBytes(path) — byte grezzi
byte[] raw = Files.readAllBytes(Path.of("photo.png"));L'equivalente in byte. Nessun Charset perché non avviene alcuna decodifica. Da usare per file binari (immagini, archivi, eseguibili) o quando occorre calcolare un hash o passare i byte a un ByteArrayInputStream.
Files.lines(path) — stream lazy
try (Stream<String> lines = Files.lines(Path.of("app.log"), StandardCharsets.UTF_8)) {
long errors = lines.filter(l -> l.contains("ERROR")).count();
}Questo è l'unico lettore integrato che scala su file arbitrariamente grandi. Lo Stream<String> è lazy — le righe vengono lette su richiesta, non tutte in una volta — e si connette direttamente al vocabolario della pipeline di stream (filter, map, count, toList).
Due aspetti non negoziabili:
try-with-resources è obbligatorio. Lo stream possiede un file handle aperto; senzatry-with-resources, il file rimane aperto fino al GC, e su un server occupato esaurirai i descrittori di file.- Non riutilizzare lo stream dopo un'operazione terminale. Gli stream sono a uso singolo.
Da usare quando: il file è troppo grande per readAllLines, o vuoi che la trasformazione riga per riga si componga con il resto della tua pipeline di stream.
BufferedReader.readLine() — il classico
BufferedReader è il componente principale su cui si appoggiano i moderni helper. Bufferizza le letture sottostanti in un chunk in memoria di dimensione fissa, in modo che readLine() non emetta una chiamata di sistema per ogni carattere.
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) è la factory moderna; la versione classica è new BufferedReader(new FileReader("hosts.txt")) (che usa il charset della piattaforma su JDK precedenti alla 18 — specifica UTF-8 con l'overload a tre argomenti). Il contratto di readLine() è:
- Restituisce la riga successiva senza il suo terminatore (
\n,\r, o\r\n). - Restituisce
nullalla fine del file. La condizione del ciclo(line = readLine()) != nullè l'idioma consolidato.
BufferedReader è anche un produttore di Stream<String>: reader.lines() restituisce uno Stream<String> supportato dal reader. Così è implementato Files.lines internamente.
Scanner — parsing token per token
Scanner legge il testo per token — parole, interi, double, righe, persino corrispondenze regex — ed è lo strumento giusto per leggere input strutturato dove le unità non sono righe intere.
try (Scanner sc = new Scanner(Files.newBufferedReader(Path.of("nums.txt")))) {
while (sc.hasNextInt()) {
int n = sc.nextInt();
System.out.println(n * n);
}
}Scanner è più lento di BufferedReader perché esegue il parsing; alloca string corte ed esegue regex. Per l'elaborazione riga per riga, preferisci BufferedReader. Per token tipizzati da un file piccolo (numeri, parole, input simile a CSV), Scanner risparmia il livello di parsing.
C'è un capitolo completo su Scanner più avanti in questa parte — questa è la variante per la lettura di file.
FileReader — il lettore di caratteri grezzo
try (FileReader in = new FileReader("notes.txt", StandardCharsets.UTF_8)) {
int c;
while ((c = in.read()) != -1) {
System.out.print((char) c);
}
}FileReader legge i caratteri direttamente dal file — nessun buffering, nessuna consapevolezza delle righe, nessuna scelta di decodifica fatta per te (passi il Charset, o accetti il default della piattaforma su JDK pre-18). È il livello su cui si appoggiano gli altri. Non lo usi quasi mai direttamente nel codice applicativo; lo avvolgi in un BufferedReader.
È ancora utile quando vuoi leggere qualche centinaio di caratteri e fermarti — lookup di piccole dimensioni dove il costo di configurazione di un buffer è trascurabile rispetto al costo della chiamata.
Quale scegliere
| Scenario | Scelta |
|---|---|
File piccolo da ottenere come singola String | Files.readString |
File piccolo da ottenere come List<String> | Files.readAllLines |
| File binario (immagine, archivio) | Files.readAllBytes |
| Qualsiasi file con trasformazione in stile stream | Files.lines (dentro try-with-resources) |
| Ciclo riga per riga, controllo completo | Files.newBufferedReader + readLine |
| Token tipizzati (int, parole, corrispondenze regex) | Scanner |
| Un carattere alla volta, file minuscolo | FileReader |
Il default giusto per il caso "voglio solo caricare questo piccolo file di testo" è Files.readString. Il default giusto per "elabora questo enorme log senza saturare la memoria" è Files.lines.
Un esempio pratico: stesso file, cinque lettori
Il programma qui sotto scrive un piccolo file di testo, poi lo legge in cinque modi diversi — readString, readAllLines, Files.lines filtrato tramite un Predicate<String> dal vocabolario della Parte 12, BufferedReader.readLine, e Scanner per interi tokenizzati. Ogni blocco stampa quello che ha ottenuto così puoi vedere le forme affiancate.
Cosa ricavare dall'esecuzione:
Files.readStringha restituito l'intero file come un'unicaString— semplice ed esattamente quello che vuoi per config e template piccoli. Per un log da 4 GB avrebbe lanciatoOutOfMemoryError.Files.readAllLinesha restituito unaList<String>indicizzabile con i terminatori rimossi.lines.get(0)ha funzionato perché la lista è materializzata in memoria; non potresti farlo con uno stream.Files.lines(file)è stato aperto dentrotry-with-resources perché lo stream possiede il file handle. La pipeline.filter(isError).count()ha la stessa forma di qualsiasi cosa della Parte 12 — solo la sorgente è cambiata.BufferedReader.readLine()ha restituitonullalla fine del file. Il cicloforqui si è fermato a tre di proposito, ma l'idioma in produzione èwhile ((line = in.readLine()) != null).Scannerha saltato le righe che non iniziavano con un intero, poi ha letto i token connextInt()fino all'esaurimento. Lo stessoScanneravrebbe potuto leggere double (nextDouble), corrispondenze regex (findInLine), oBigInteger— ecco perché costa di più per token rispetto a quantoBufferedReadercosta per riga.
Cosa viene dopo
Il prossimo capitolo, Scrivere File in Java, copre il lato della scrittura delle stesse API — Files.writeString, Files.write, BufferedWriter, PrintWriter, e i flag StandardOpenOption (APPEND, CREATE_NEW, TRUNCATE_EXISTING) che decidono come viene gestito un file esistente.