1. Lettura del file riga per riga: BufferedReader e affini
In precedenza abbiamo già visto la lettura byte per byte: è comoda per lavorare con formati binari, ma per il testo questo approccio è scomodo. Un file è composto da caratteri che dipendono dalla codifica. Per questo Java offre comode «astrazioni» — FileReader, BufferedReader e altre classi che trasformano un flusso di byte in un flusso di caratteri e righe.
Immaginate un file di testo — che si tratti di un log di applicazione, di un elenco di utenti o persino del grande romanzo «Guerra e pace». A volte occorre leggere rapidamente l’intero file, altre volte — scorrerlo riga per riga, e talvolta — estrarre una singola riga.
In Java esistono diversi modi per farlo e la scelta dipende dalla dimensione del file e dallo scopo. Se è necessario elaborare il file una riga alla volta (ad esempio contare le righe o trovare una voce), si usa la lettura riga per riga. Se invece il file è piccolo — lo si può caricare interamente in memoria e trattarlo come un elenco di righe.
Perché la lettura riga per riga è una buona idea?
Per i file di grandi dimensioni, la lettura riga per riga evita problemi di memoria. Caricare in memoria un log da gigabyte — è una cattiva idea: è facile incorrere in OutOfMemoryError. Invece leggere il file riga dopo riga è poco costoso, anche se pesa centinaia di megabyte.
Come si fa in Java?
Il modo più classico — è usare BufferedReader (o affini) e il metodo readLine().
import java.io.*;
public class ReadLinesDemo {
public static void main(String[] args) {
String fileName = "example.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
int lineNumber = 1;
while ((line = reader.readLine()) != null) {
System.out.printf("%3d: %s%n", lineNumber, line);
lineNumber++;
}
} catch (IOException e) {
System.out.println("Errore durante la lettura del file: " + e.getMessage());
}
}
}
Qui apriamo il file in lettura e con readLine() leggiamo una riga alla volta finché non raggiungiamo la fine (null). Poi stampiamo ogni riga con il suo numero.
Breve nota su try-with-resources
Vedete la costruzione try (...) { ... }? È il try-with-resources. Garantisce che il file venga chiuso, anche se si verifica un errore durante la lettura. Non è necessario chiamare close() manualmente: la chiusura avverrà automaticamente, anche in presenza di catch/finally.
A cosa serve BufferedReader?
BufferedReader non legge un carattere alla volta, ma a blocchi (di solito di 8192 byte), il che accelera il lavoro con i file. Inoltre, offre il comodo metodo readLine(), che restituisce la riga fino al carattere di fine riga.
Quale buffer scegliere?
Di solito BufferedReader usa un buffer di 8192 byte (8 KB) — sufficiente per la maggior parte dei casi. Se leggete righe molto lunghe (ad esempio di 100_000 caratteri), è possibile aumentare il buffer:
BufferedReader reader = new BufferedReader(new FileReader(fileName), 65536); // buffer da 64 KB
Ma per le attività comuni la dimensione predefinita va benissimo.
2. Lettura del file per intero: Files.readAllLines e Files.readString
Se il file è piccolo (ad esempio fino a 10–20 MB), è comodo caricarlo interamente. Per esempio, se serve ottenere rapidamente l’elenco di tutte le righe, analizzare il testo o inviarlo in rete.
Metodo moderno: Files.readAllLines
Con Java 7 è arrivata la comoda classe Files con molti metodi utili.
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class ReadAllLinesDemo {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try {
List<String> lines = Files.readAllLines(path);
for (int i = 0; i < lines.size(); i++) {
System.out.printf("%3d: %s%n", i + 1, lines.get(i));
}
} catch (IOException e) {
System.out.println("Errore durante la lettura del file: " + e.getMessage());
}
}
}
Qui il metodo Files.readAllLines(path) restituisce un elenco di righe (List<String>). Con questo elenco si può lavorare come con una normale collezione: cercare, ordinare, filtrare.
Ancora più moderno: Files.readString (Java 11+)
Se vi serve ottenere tutto il file in un’unica stringa (ad esempio per cercare una sottostringa o per inviarlo in JSON), usate Files.readString:
import java.nio.file.*;
import java.io.IOException;
public class ReadStringDemo {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try {
String content = Files.readString(path);
System.out.println("Contenuto del file:");
System.out.println(content);
} catch (IOException e) {
System.out.println("Errore durante la lettura del file: " + e.getMessage());
}
}
}
E la codifica?
Per impostazione predefinita i metodi Files.readAllLines e Files.readString usano la codifica predefinita del sistema. Se il file è in un’altra codifica (ad esempio, Windows-1251), specificatela esplicitamente:
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
String content = Files.readString(path, StandardCharsets.UTF_8);
3. Confronto degli approcci: quando usare cosa
| Metodo | Quando usarlo | Vantaggi | Svantaggi |
|---|---|---|---|
|
File grandi, elaborazione riga per riga | Risparmia memoria, flessibile | Lettura solo per righe |
|
File piccoli e medi | Subito un elenco di righe, semplice | Per file grandi — OutOfMemory |
|
File piccoli, serve tutto il testo | Intero contenuto in un’unica stringa | Nessuna suddivisione in righe |
Raccomandazione:
— Se il file è piccolo — usate Files.readAllLines o Files.readString.
— Se il file è grande o non ne conoscete la dimensione — usate BufferedReader.readLine().
4. Attività pratiche: esempi e analisi
Esempio 1. Conteggio delle righe in un file grande
Supponiamo di voler sapere quante righe ci sono in un file enorme (ad esempio un log di server).
import java.io.*;
public class LineCount {
public static void main(String[] args) {
String fileName = "biglog.txt";
int lineCount = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
while (reader.readLine() != null) {
lineCount++;
}
System.out.println("Totale righe nel file: " + lineCount);
} catch (IOException e) {
System.out.println("Errore durante la lettura del file: " + e.getMessage());
}
}
}
Esempio 2. Ricerca di righe per contenuto
Cerchiamo tutte le righe in cui compare la parola «errore» (case-insensitive):
import java.io.*;
public class FindErrorLines {
public static void main(String[] args) {
String fileName = "biglog.txt";
String keyword = "errore";
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
int lineNumber = 1;
while ((line = reader.readLine()) != null) {
if (line.toLowerCase().contains(keyword)) {
System.out.printf("%3d: %s%n", lineNumber, line);
}
lineNumber++;
}
} catch (IOException e) {
System.out.println("Errore durante la lettura del file: " + e.getMessage());
}
}
}
Esempio 3. Caricamento di una configurazione da un piccolo file
File config.txt:
host=localhost
port=8080
mode=dev
Lo leggiamo interamente e lo analizziamo in coppie chiave–valore:
import java.nio.file.*;
import java.util.*;
public class ConfigLoader {
public static void main(String[] args) throws Exception {
Path path = Path.of("config.txt");
List<String> lines = Files.readAllLines(path);
Map<String, String> config = new HashMap<>();
for (String line : lines) {
if (line.trim().isEmpty() || line.startsWith("#")) continue; // saltiamo le righe vuote e i commenti
String[] parts = line.split("=", 2);
if (parts.length == 2) {
config.put(parts[0].trim(), parts[1].trim());
}
}
System.out.println("Configurazione caricata: " + config);
}
}
5. Errori tipici nella lettura di file di testo
Errore n° 1: tentare di leggere un file binario come testo. Se aprite un’immagine o un archivio tramite BufferedReader o Files.readAllLines, otterrete simboli senza senso e rischiate un OutOfMemoryError. Per i file binari usate InputStream!
Errore n° 2: eccezione non gestita. I file possono essere eliminati, spostati, bloccati. Incapsulate sempre la lettura in try-catch e informate l’utente dei problemi.
Errore n° 3: ignorare la codifica. Se avete testo in russo e leggete un file senza specificarne la codifica, potreste ottenere «?????». Usate StandardCharsets.UTF_8 o la codifica necessaria.
Errore n° 4: dimenticare di chiudere lo stream. Se non si chiude il file, potrebbe rimanere bloccato fino al termine del programma. Usate sempre il try-with-resources.
Errore n° 5: usare read() invece di readLine() per i file di testo. Il metodo read() legge un carattere alla volta — è lento e scomodo per le stringhe. Per il testo usate readLine() o i metodi della classe Files.
GO TO FULL VERSION