CodeGym /Corsi /JAVA 25 SELF /Lettura di file di testo: riga per riga, interamente

Lettura di file di testo: riga per riga, interamente

JAVA 25 SELF
Livello 36 , Lezione 1
Disponibile

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 1020 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
BufferedReader.readLine()
File grandi, elaborazione riga per riga Risparmia memoria, flessibile Lettura solo per righe
Files.readAllLines()
File piccoli e medi Subito un elenco di righe, semplice Per file grandi — OutOfMemory
Files.readString()
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.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION