W3docs

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:

  1. Files.readString(path) — l'intero file come un'unica String.
  2. Files.readAllLines(path) — l'intero file come una List<String>.
  3. Files.readAllBytes(path) — l'intero file come un byte[].
  4. Files.lines(path) — il file come uno Stream<String>, lazy.
  5. 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; senza try-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 null alla 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

ScenarioScelta
File piccolo da ottenere come singola StringFiles.readString
File piccolo da ottenere come List<String>Files.readAllLines
File binario (immagine, archivio)Files.readAllBytes
Qualsiasi file con trasformazione in stile streamFiles.lines (dentro try-with-resources)
Ciclo riga per riga, controllo completoFiles.newBufferedReader + readLine
Token tipizzati (int, parole, corrispondenze regex)Scanner
Un carattere alla volta, file minuscoloFileReader

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.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Files.readString ha restituito l'intero file come un'unica String — semplice ed esattamente quello che vuoi per config e template piccoli. Per un log da 4 GB avrebbe lanciato OutOfMemoryError.
  • Files.readAllLines ha restituito una List<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 dentro try-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 restituito null alla fine del file. Il ciclo for qui si è fermato a tre di proposito, ma l'idioma in produzione è while ((line = in.readLine()) != null).
  • Scanner ha saltato le righe che non iniziavano con un intero, poi ha letto i token con nextInt() fino all'esaurimento. Lo stesso Scanner avrebbe potuto leggere double (nextDouble), corrispondenze regex (findInLine), o BigInteger — ecco perché costa di più per token rispetto a quanto BufferedReader costa 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.

Esercizi

Pratica
Devi elaborare un log del server da 5 GB riga per riga, contando quante righe contengono la parola `ERROR`. Qual è il lettore giusto?
Devi elaborare un log del server da 5 GB riga per riga, contando quante righe contengono la parola `ERROR`. Qual è il lettore giusto?
Was this page helpful?