Salta al contenuto principale

09 - Input/Output e NIO.2

I/O in Java: stream di byte e caratteri, BufferedReader/Writer, NIO.2 con Path e Files, serializzazione oggetti e best practices.

Cosa ne pensi di questo articolo?

09 - Input/Output e NIO.2
JavaFoto:Godfrey AtimaPexels

In Breve

I/O in Java: stream di byte e caratteri, BufferedReader/Writer, NIO.2 con Path e Files, serializzazione oggetti e best practices.

Input/Output in Java

Java offre due principali API per l'I/O: il pacchetto java.io (stream-based) e java.nio (New I/O, buffer-based). In questo articolo esploreremo entrambi gli approcci.

Cosa Imparerai

  • Stream di byte e caratteri
  • Lettura e scrittura di file
  • BufferedReader e BufferedWriter
  • NIO.2: Path e Files
  • Serializzazione di oggetti
  • Best practices per I/O

I/O Classico: java.io

Gerarchia degli Stream

Tipo Byte Stream Character Stream
Input InputStream Reader
Output OutputStream Writer
File FileInputStream/FileOutputStream FileReader/FileWriter
Buffered BufferedInputStream/BufferedOutputStream BufferedReader/BufferedWriter

Lettura di File di Testo

FileReader e BufferedReader
import java.io.*;

public class LettoreFileStudenti {

    public static void leggiElencoStudenti(String percorso) {
        // try-with-resources per chiusura automatica
        try (BufferedReader reader = new BufferedReader(new FileReader(percorso))) {

            String linea;
            int numeroLinea = 1;

            while ((linea = reader.readLine()) != null) {
                System.out.println(numeroLinea + ": " + linea);
                numeroLinea++;
            }

        } catch (FileNotFoundException e) {
            System.err.println("File non trovato: " + percorso);
        } catch (IOException e) {
            System.err.println("Errore lettura: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        leggiElencoStudenti("studenti.txt");
    }
}

Scrittura di File di Testo

FileWriter e BufferedWriter
import java.io.*;
import java.util.*;

public class ScrittoreFileVoti {

    public static void salvaVoti(String percorso, Map<String, Integer> voti) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(percorso))) {

            writer.write("=== REGISTRO VOTI ===");
            writer.newLine();
            writer.newLine();

            for (Map.Entry<String, Integer> entry : voti.entrySet()) {
                String riga = entry.getKey() + ": " + entry.getValue();
                writer.write(riga);
                writer.newLine();
            }

            writer.newLine();
            writer.write("Generato automaticamente");
            System.out.println("File salvato: " + percorso);

        } catch (IOException e) {
            System.err.println("Errore scrittura: " + e.getMessage());
        }
    }

    // Append mode: aggiunge al file esistente
    public static void aggiungiVoto(String percorso, String studente, int voto) {
        try (BufferedWriter writer = new BufferedWriter(
                new FileWriter(percorso, true))) {  // true = append

            writer.write(studente + ": " + voto);
            writer.newLine();

        } catch (IOException e) {
            System.err.println("Errore: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        Map<String, Integer> voti = new LinkedHashMap<>();
        voti.put("Mario Rossi", 28);
        voti.put("Laura Bianchi", 30);
        voti.put("Giuseppe Verdi", 25);

        salvaVoti("voti.txt", voti);
    }
}

Stream di Byte

InputStream e OutputStream
import java.io.*;

public class CopiatoreFile {

    public static void copiaFile(String sorgente, String destinazione) {
        try (
            FileInputStream fis = new FileInputStream(sorgente);
            FileOutputStream fos = new FileOutputStream(destinazione);
            BufferedInputStream bis = new BufferedInputStream(fis);
            BufferedOutputStream bos = new BufferedOutputStream(fos)
        ) {
            byte[] buffer = new byte[8192];  // 8KB buffer
            int byteLetti;

            while ((byteLetti = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, byteLetti);
            }

            System.out.println("File copiato con successo");

        } catch (IOException e) {
            System.err.println("Errore copia: " + e.getMessage());
        }
    }

    // Lettura completa in byte array
    public static byte[] leggiTuttiBytes(String percorso) throws IOException {
        try (FileInputStream fis = new FileInputStream(percorso)) {
            return fis.readAllBytes();  // Java 9+
        }
    }

    public static void main(String[] args) {
        copiaFile("originale.pdf", "copia.pdf");
    }
}

NIO.2: L'API Moderna

Introdotto in Java 7, NIO.2 fornisce un'API più moderna e potente per le operazioni su file e directory.

Path e Paths

Lavorare con i Path
import java.nio.file.*;

public class GestorePercorsi {

    public static void main(String[] args) {
        // Creare Path
        Path path1 = Path.of("documenti", "studenti", "voti.txt");
        Path path2 = Paths.get("/home/user/documenti/studenti/voti.txt");
        Path path3 = Path.of("./dati/../documenti/voti.txt");

        // Informazioni sul path
        System.out.println("Nome file: " + path1.getFileName());
        System.out.println("Directory: " + path1.getParent());
        System.out.println("Root: " + path2.getRoot());
        System.out.println("Assoluto: " + path1.toAbsolutePath());

        // Normalizzazione (rimuove . e ..)
        Path normalizzato = path3.normalize();
        System.out.println("Normalizzato: " + normalizzato);

        // Risoluzione (combinare path)
        Path base = Path.of("/scuola");
        Path relativo = Path.of("studenti/mario/voti.txt");
        Path completo = base.resolve(relativo);
        System.out.println("Path completo: " + completo);

        // Relativizzare
        Path dir1 = Path.of("/scuola/studenti");
        Path dir2 = Path.of("/scuola/docenti/storia");
        Path relPath = dir1.relativize(dir2);
        System.out.println("Relativo: " + relPath);  // ../docenti/storia

        // Iterazione componenti
        for (Path component : path1) {
            System.out.println("Componente: " + component);
        }
    }
}

Files: Operazioni su File

Lettura e Scrittura con Files
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class OperazioniFile {

    public static void main(String[] args) throws Exception {
        Path file = Path.of("studenti.txt");

        // === SCRITTURA ===

        // Scrivere stringa completa
        String contenuto = "Mario Rossi\nLaura Bianchi\nGiuseppe Verdi";
        Files.writeString(file, contenuto);

        // Scrivere lista di linee
        List<String> linee = Arrays.asList(
            "Mario Rossi: 28",
            "Laura Bianchi: 30",
            "Giuseppe Verdi: 25"
        );
        Files.write(Path.of("voti.txt"), linee);

        // Append
        Files.writeString(file, "\nNuovo Studente",
            StandardOpenOption.APPEND);

        // === LETTURA ===

        // Leggere stringa completa
        String tuttoContenuto = Files.readString(file);
        System.out.println(tuttoContenuto);

        // Leggere tutte le linee
        List<String> tutteLinee = Files.readAllLines(file);
        for (String linea : tutteLinee) {
            System.out.println(linea);
        }

        // Stream di linee (lazy, per file grandi)
        Files.lines(file)
             .filter(l -> l.contains("Rossi"))
             .forEach(System.out::println);

        // Leggere bytes
        byte[] bytes = Files.readAllBytes(Path.of("documento.pdf"));
    }
}
Gestione File e Directory
import java.nio.file.*;
import java.nio.file.attribute.*;

public class GestioneFileDirectory {

    public static void main(String[] args) throws Exception {
        Path dir = Path.of("scuola/studenti");
        Path file = dir.resolve("elenco.txt");

        // === VERIFICA ESISTENZA E TIPO ===
        System.out.println("Esiste: " + Files.exists(file));
        System.out.println("È file: " + Files.isRegularFile(file));
        System.out.println("È directory: " + Files.isDirectory(dir));
        System.out.println("Leggibile: " + Files.isReadable(file));
        System.out.println("Scrivibile: " + Files.isWritable(file));

        // === CREARE DIRECTORY ===
        Files.createDirectory(Path.of("nuova_dir"));  // Una sola
        Files.createDirectories(dir);  // Con genitori

        // === CREARE FILE ===
        if (!Files.exists(file)) {
            Files.createFile(file);
        }

        // === COPIARE ===
        Path copia = Path.of("scuola/backup/elenco_backup.txt");
        Files.createDirectories(copia.getParent());
        Files.copy(file, copia, StandardCopyOption.REPLACE_EXISTING);

        // === SPOSTARE/RINOMINARE ===
        Path nuovoPosizione = Path.of("archivio/elenco.txt");
        Files.createDirectories(nuovoPosizione.getParent());
        Files.move(copia, nuovoPosizione,
            StandardCopyOption.REPLACE_EXISTING);

        // === ELIMINARE ===
        Files.delete(nuovoPosizione);  // Eccezione se non esiste
        Files.deleteIfExists(nuovoPosizione);  // Silenzioso

        // === ATTRIBUTI ===
        BasicFileAttributes attrs = Files.readAttributes(
            file, BasicFileAttributes.class
        );
        System.out.println("Dimensione: " + attrs.size());
        System.out.println("Creato: " + attrs.creationTime());
        System.out.println("Modificato: " + attrs.lastModifiedTime());
    }
}

Esplorare Directory

Listare e Cercare File
import java.nio.file.*;
import java.io.IOException;

public class EsploraDirectory {

    public static void main(String[] args) throws IOException {
        Path dir = Path.of("scuola");

        // === LISTARE CONTENUTI ===

        // Lista diretta (non ricorsiva)
        System.out.println("=== Contenuto directory ===");
        try (var stream = Files.list(dir)) {
            stream.forEach(System.out::println);
        }

        // Lista ricorsiva (walk)
        System.out.println("\n=== Walk ricorsivo ===");
        try (var stream = Files.walk(dir)) {
            stream.forEach(System.out::println);
        }

        // Walk con profondità massima
        try (var stream = Files.walk(dir, 2)) {
            stream.filter(Files::isRegularFile)
                  .forEach(System.out::println);
        }

        // === CERCARE FILE ===

        // Con glob pattern
        System.out.println("\n=== File .txt ===");
        try (var stream = Files.newDirectoryStream(dir, "*.txt")) {
            for (Path file : stream) {
                System.out.println(file);
            }
        }

        // Find con predicato
        System.out.println("\n=== File > 1KB ===");
        try (var stream = Files.find(dir, 10,
                (path, attrs) -> attrs.isRegularFile() &&
                                 attrs.size() > 1024)) {
            stream.forEach(System.out::println);
        }
    }
}

Serializzazione

La serializzazione permette di salvare oggetti Java su file e rileggerli successivamente.

Classe Serializzabile
import java.io.*;

public class Studente implements Serializable {
    // UID per versioning
    private static final long serialVersionUID = 1L;

    private String nome;
    private String matricola;
    private int annoIscrizione;

    // transient = non serializzato
    private transient String passwordTemporanea;

    public Studente(String nome, String matricola, int annoIscrizione) {
        this.nome = nome;
        this.matricola = matricola;
        this.annoIscrizione = annoIscrizione;
    }

    // Getters
    public String getNome() { return nome; }
    public String getMatricola() { return matricola; }
    public int getAnnoIscrizione() { return annoIscrizione; }

    @Override
    public String toString() {
        return String.format("Studente[%s, %s, %d]",
            nome, matricola, annoIscrizione);
    }
}
Serializzazione e Deserializzazione
import java.io.*;
import java.util.*;

public class GestoreSerializzazione {

    // Salvare un oggetto
    public static void salvaStudente(Studente studente, String file) {
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(file))) {

            oos.writeObject(studente);
            System.out.println("Studente salvato: " + file);

        } catch (IOException e) {
            System.err.println("Errore salvataggio: " + e.getMessage());
        }
    }

    // Caricare un oggetto
    public static Studente caricaStudente(String file) {
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream(file))) {

            return (Studente) ois.readObject();

        } catch (IOException | ClassNotFoundException e) {
            System.err.println("Errore caricamento: " + e.getMessage());
            return null;
        }
    }

    // Salvare lista di oggetti
    public static void salvaElenco(List<Studente> studenti, String file) {
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(file))) {

            oos.writeObject(studenti);
            System.out.println("Elenco salvato: " + studenti.size() + " studenti");

        } catch (IOException e) {
            System.err.println("Errore: " + e.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    public static List<Studente> caricaElenco(String file) {
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream(file))) {

            return (List<Studente>) ois.readObject();

        } catch (IOException | ClassNotFoundException e) {
            System.err.println("Errore: " + e.getMessage());
            return Collections.emptyList();
        }
    }

    public static void main(String[] args) {
        // Salvare singolo studente
        Studente mario = new Studente("Mario Rossi", "MAT001", 2024);
        salvaStudente(mario, "mario.dat");

        // Caricare
        Studente caricato = caricaStudente("mario.dat");
        System.out.println("Caricato: " + caricato);

        // Salvare lista
        List<Studente> studenti = Arrays.asList(
            new Studente("Mario Rossi", "MAT001", 2024),
            new Studente("Laura Bianchi", "MAT002", 2024),
            new Studente("Giuseppe Verdi", "MAT003", 2023)
        );
        salvaElenco(studenti, "studenti.dat");

        // Caricare lista
        List<Studente> caricati = caricaElenco("studenti.dat");
        caricati.forEach(System.out::println);
    }
}

Esempio Completo: Sistema di Registro Scolastico

RegistroScolastico.java
import java.io.*;
import java.nio.file.*;
import java.util.*;

public class RegistroScolastico {

    private static final Path DATA_DIR = Path.of("dati_scuola");
    private static final Path STUDENTI_FILE = DATA_DIR.resolve("studenti.csv");
    private static final Path VOTI_FILE = DATA_DIR.resolve("voti.csv");

    public static void inizializza() throws IOException {
        Files.createDirectories(DATA_DIR);

        if (!Files.exists(STUDENTI_FILE)) {
            Files.writeString(STUDENTI_FILE, "matricola,nome,classe\n");
        }
        if (!Files.exists(VOTI_FILE)) {
            Files.writeString(VOTI_FILE, "matricola,materia,voto,data\n");
        }
    }

    public static void aggiungiStudente(String matricola, String nome, String classe)
            throws IOException {
        String riga = String.format("%s,%s,%s%n", matricola, nome, classe);
        Files.writeString(STUDENTI_FILE, riga, StandardOpenOption.APPEND);
        System.out.println("Studente aggiunto: " + nome);
    }

    public static void registraVoto(String matricola, String materia, int voto)
            throws IOException {
        String data = java.time.LocalDate.now().toString();
        String riga = String.format("%s,%s,%d,%s%n", matricola, materia, voto, data);
        Files.writeString(VOTI_FILE, riga, StandardOpenOption.APPEND);
        System.out.println("Voto registrato: " + materia + " = " + voto);
    }

    public static void stampaStudenti() throws IOException {
        System.out.println("\n=== ELENCO STUDENTI ===");
        Files.lines(STUDENTI_FILE)
             .skip(1)  // Salta header
             .forEach(linea -> {
                 String[] parti = linea.split(",");
                 if (parti.length >= 3) {
                     System.out.printf("%-10s %-20s %s%n",
                         parti[0], parti[1], parti[2]);
                 }
             });
    }

    public static void stampaVotiStudente(String matricola) throws IOException {
        System.out.println("\n=== VOTI di " + matricola + " ===");
        Files.lines(VOTI_FILE)
             .skip(1)
             .filter(linea -> linea.startsWith(matricola + ","))
             .forEach(linea -> {
                 String[] parti = linea.split(",");
                 if (parti.length >= 4) {
                     System.out.printf("%-15s %s (%s)%n",
                         parti[1], parti[2], parti[3]);
                 }
             });
    }

    public static double calcolaMedia(String matricola) throws IOException {
        return Files.lines(VOTI_FILE)
             .skip(1)
             .filter(linea -> linea.startsWith(matricola + ","))
             .mapToInt(linea -> {
                 String[] parti = linea.split(",");
                 return Integer.parseInt(parti[2]);
             })
             .average()
             .orElse(0);
    }

    public static void backupDati() throws IOException {
        Path backupDir = DATA_DIR.resolve("backup_" +
            java.time.LocalDate.now().toString());
        Files.createDirectories(backupDir);

        Files.copy(STUDENTI_FILE,
            backupDir.resolve("studenti.csv"),
            StandardCopyOption.REPLACE_EXISTING);
        Files.copy(VOTI_FILE,
            backupDir.resolve("voti.csv"),
            StandardCopyOption.REPLACE_EXISTING);

        System.out.println("Backup creato: " + backupDir);
    }

    public static void main(String[] args) {
        try {
            // Inizializza
            inizializza();

            // Aggiungi studenti
            aggiungiStudente("MAT001", "Mario Rossi", "3A");
            aggiungiStudente("MAT002", "Laura Bianchi", "3A");
            aggiungiStudente("MAT003", "Giuseppe Verdi", "3B");

            // Registra voti
            registraVoto("MAT001", "Matematica", 28);
            registraVoto("MAT001", "Italiano", 26);
            registraVoto("MAT001", "Informatica", 30);
            registraVoto("MAT002", "Matematica", 30);
            registraVoto("MAT002", "Italiano", 29);

            // Stampa report
            stampaStudenti();
            stampaVotiStudente("MAT001");

            // Calcola media
            double media = calcolaMedia("MAT001");
            System.out.printf("%nMedia MAT001: %.2f%n", media);

            // Backup
            backupDati();

        } catch (IOException e) {
            System.err.println("Errore I/O: " + e.getMessage());
        }
    }
}

Best Practices

Regole per I/O Efficiente

  • Usa try-with-resources: Chiusura automatica garantita
  • Usa BufferedReader/Writer: Migliori performance
  • Preferisci NIO.2: API più moderna e pulita
  • Gestisci le eccezioni: IOException è molto comune
  • Specifica l'encoding: Usa StandardCharsets.UTF_8
  • Usa Files.lines() per file grandi: Lazy stream
  • Chiudi sempre gli stream: Evita memory leak
  • Verifica esistenza prima di operare: Files.exists()

Conclusione

L'I/O in Java offre strumenti potenti per lavorare con file e stream. L'API NIO.2 è l'approccio consigliato per il codice moderno.

Punti Chiave da Ricordare

  • java.io: Stream classici, byte e caratteri
  • BufferedReader/Writer: Performance migliori per testo
  • NIO.2 Path: Rappresentazione moderna dei percorsi
  • NIO.2 Files: Utility statiche per operazioni comuni
  • Serializable: Per salvare oggetti Java
  • StandardOpenOption: Controllo preciso su apertura file

Nel prossimo articolo esploreremo la programmazione funzionale in Java: lambda, Stream API e Optional.

Domande frequenti

Quali sono le due principali API di input/output offerte da Java?

Il pacchetto java.io (I/O classico) e NIO.2.

In che anno è stato introdotto l'API NIO.2 in Java?

L'API NIO.2 è stata introdotta in Java 7.

Quali sono le principali funzionalità offerte dall'API NIO.2?

Le principali funzionalità offerte dall'API NIO.2 includono la gestione dei file e delle directory, la serializzazione degli oggetti e l'esplorazione delle directory.

Qual è il vantaggio dell'utilizzo dell'API NIO.2 rispetto all'I/O classico?

L'API NIO.2 offre un approccio più moderno e potente per le operazioni su file e directory, rendendola l'opzione consigliata per il codice moderno.

Fonti e approfondimenti

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti