10  Entrada, sortida i fitxers

Objectius

  • Entendre el concepte de flux (stream) de dades i la diferència entre fluxos de bytes i de caràcters.
  • Llegir dades de teclat amb Scanner de manera robusta.
  • Formatar la sortida amb printf i DecimalFormat.
  • Representar rutes amb File i Path.
  • Llegir i escriure fitxers de text.
  • Crear, esborrar i comprovar l’existència de fitxers i directoris.
  • Tancar recursos automàticament amb el try-with-resources.

Fins ara els nostres programes tot passava en memòria i en acabar s’esborrava. Per fer coses útils, un programa ha de comunicar-se amb l’exterior: llegir el que escriu l’usuari, mostrar resultats amb bon format i, sobretot, guardar informació que sobrevisqui al tancament del programa. Això últim és el que fan els fitxers.

10.1 El concepte de flux o stream

Java modela tota entrada i sortida de dades com un flux: un corrent de dades que va d’un origen a un destí, peça a peça i en ordre.

NotaAnalogia: la canonada d’aigua

Un flux és com una canonada. Per un extrem entra l’aigua (les dades) i per l’altre surt. No veus tota l’aigua alhora: passa gota a gota, en ordre.

Un flux d’entrada porta dades cap al programa (del teclat, d’un fitxer); un flux de sortida les treu del programa (a la pantalla, a un fitxer).

flowchart LR
    T["Teclat"] -->|flux d'entrada| P["El teu<br/>programa"]
    F1["Fitxer"] -->|flux d'entrada| P
    P -->|flux de sortida| PA["Pantalla"]
    P -->|flux de sortida| F2["Fitxer"]

Bytes o caràcters

Hi ha dues grans famílies de fluxos, segons la unitat de dades que transporten:

Concepte Flux de bytes Flux de caràcters
Unitat Bbyte (8 bits) caràcter (lletra, respecta accents)
Per a què Dades binàries: imatges, àudio, vídeo, PDF Text: .txt, .csv, codi font
Classes base InputStream / OutputStream Reader / Writer
Exemple FileInputStream FileReader, BufferedReader
ConsellRegla pràctica

Si obres el fitxer amb un editor de text i l’entens (lletres, números), és text → fes servir fluxos de caràcters.

Si l’obres i veus símbols estranys (una imatge, una cançó), és binari → fluxos de bytes.

En aquest capítol treballarem sobretot amb text.

Els accents i la ce trencada són un motiu important per triar bé: els fluxos de caràcters saben interpretar la codificació (com UTF-8) i respecten la ç, la à o la ï. Els de bytes veuen només nombres.

10.2 Entrada per teclat amb Scanner

Ja vam utilitzar la classe Scanner en la introducció a Java. És la forma més còmoda per llegir el que escriu l’usuari. Recorda que primer cal importar-lo.

Es connecta al flux d’entrada estàndard (el teclat), que és System.in:

import java.util.Scanner;
...

Scanner sc = new Scanner(System.in);

System.out.print("Com et dius? ");
String nom = sc.nextLine();

System.out.print("Quants anys tens? ");
int edat = sc.nextInt();

System.out.println("Hola " + nom + ", tens " + edat + " anys.");

Els mètodes més útils de Scanner:

Mètode Llegeix
nextLine() Tota una línia de text (fins a l’Intro)
next() Una sola paraula
nextInt() Un enter
nextDouble() Un decimal
nextBoolean() true o false
AlertaError típic: nextInt() seguit de nextLine()

Quan fas nextInt(), Scanner llegeix el nombre però deixa el salt de línia pendent.

Si just després fas nextLine(), aquest llegeix aquell salt buit i sembla que “s’ha saltat” la pregunta:

int edat = sc.nextInt();
System.out.print("Ciutat? ");
String ciutat = sc.nextLine();   // Llegeix "" i no espera l'usuari.

Solució: afegeix un sc.nextLine() extra després del nextInt() per consumir el salt pendent.

10.3 Formatar la sortida

System.out.println va bé, però quan volem controlar exactament com es veuen els nombres (decimals, alineació, símbol de moneda) ens queden curts.

Java ens ofereix les eines:

  • Mètode printf de System.out.
  • Classe DecimalFormat.

El mètode printf (print formatted) imprimeix segons una plantilla amb marcadors que comencen per %:

double preu = 19.5;
int unitats = 3;
System.out.printf("%d unitats a %.2f € = %.2f%n", unitats, preu, unitats * preu);
// Sortida: 3 unitats a 19,50 € = 58,50 €

Els marcadors més habituals són:

Marcador Significa Exemple
%d Enter 42
%f Decimal 3.140000
%.2f Decimal amb 2 xifres 3.14
%s Text (String) hola
%n Salt de línia
%5d Enter en 5 espais (alineat a la dreta) 42
NotaPer què %n i no \n?

%n produeix el salt de línia correcte del sistema operatiu on s’executa el programa (Windows i Linux fan servir salts diferents).

Dins de printf és preferible a \n.

La classe DecimalFormat en permet un control més afinat del format d’un nombre: separador de milers, decimals fixos, símbols,..

import java.text.DecimalFormat;

DecimalFormat df = new DecimalFormat("#,##0.00");
System.out.println(df.format(1234567.5));   // 1.234.567,50

Els patrons es construeixen amb símbols: 0 (xifra obligatòria), # (xifra opcional), , (separador de milers), . (separador decimal).

ConsellMini-repte

Mostra el resultat de dividir 10 entre 3 amb exactament 4 decimals, primer amb printf i després amb DecimalFormat. Compara els dos resultats.

10.4 Rutes: File i Path

Per treballar amb fitxers, primer hem de saber on són. Una ruta és l’adreça d’un fitxer dins del sistema d’arxius. Java ofereix dues maneres de representar-les:

  • File (paquet java.io): la manera clàssica, encara molt usada.
  • Path (paquet java.nio.file): la manera moderna, més potent i recomanada.
import java.io.File;
import java.nio.file.Path;

File fitxerVell = new File("dades/socis.txt");
Path fitxerNou = Path.of("dades/socis.txt");
AlertaError típic: barres invertides a Windows

A Windows les rutes fan servir \ (C:\Users\...), però a Java la barra invertida dins d’un String és un caràcter especial.

Si escrius "C:\nova", el \n es converteix en un salt de línia. Per solucionar-ho:

  • Fes servir barres normals /, que funcionen a tots els sistemes: "C:/Users/dades.txt".
  • O bé duplica la barra: "C:\\Users\\dades.txt".

La recomanació és treballar amb rutes relatives ("dades/socis.txt") sempre que puguis.

Amb File pots consultar informació d’un fitxer sense ni tan sols obrir-lo:

File f = new File("dades/socis.txt");
System.out.println("Existeix? " + f.exists());
System.out.println("És un directori? " + f.isDirectory());
System.out.println("Mida en bytes: " + f.length());

10.5 Llegir un fitxer de text

Per llegir text, la combinació més còmoda és BufferedReader i FileReader. Llegeix línia a línia i de manera eficient.

Com que treballem amb fitxers, hem de gestionar la IOException.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Llegir {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("dades/socis.txt"))) {
            String linia;
            while ((linia = br.readLine()) != null) {   // null = final del fitxer
                System.out.println(linia);
            }
        } catch (IOException e) {
            System.out.println("No s'ha pogut llegir el fitxer: " + e.getMessage());
        }
    }
}

El bucle while ((linia = br.readLine()) != null) és un patró que veuràs molt: readLine() retorna la línia següent, o null quan ja no en queden.

Així recorrem el fitxer sencer sense saber per endavant quantes línies té.

ConsellEl try-with-resources

Has vist el try (...) amb parèntesis? Es diu try-with-resources.

Qualsevol recurs que obris entre aquests parèntesis (com el BufferedReader) es tanca automàticament en acabar, tant si hi ha hagut error com si no.

És la manera moderna i segura de treballar amb fitxers: t’estalvia el finally per tancar-los a mà i evita fuites de recursos.

Una alternativa encara més curta, quan el fitxer no és enorme, és llegir-lo tot de cop amb Files.readAllLines, que et retorna una llista de línies:

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

List<String> linies = Files.readAllLines(Path.of("dades/socis.txt"));
for (String l : linies) {
    System.out.println(l);
}

10.6 Escriure un fitxer de text

Per escriure fem servir BufferedWriter (o el pràctic PrintWriter, que té println i printf com System.out):

import java.io.PrintWriter;
import java.io.FileWriter;
import java.io.IOException;

public class Escriure {
    public static void main(String[] args) {
        try (PrintWriter pw = new PrintWriter(new FileWriter("dades/sortida.txt"))) {
            pw.println("Anna Puig;25");
            pw.println("Bru Camps;31");
            pw.printf("Total socis: %d%n", 2);
        } catch (IOException e) {
            System.out.println("No s'ha pogut escriure: " + e.getMessage());
        }
    }
}
AlertaError típic: sobreescriure sense voler

Per defecte, new FileWriter("fitxer.txt") esborra tot el contingut anterior del fitxer i comença de zero.

Si el que vols és afegir al final sense perdre el que hi havia, passa true com a segon argument:

new FileWriter("fitxer.txt", true)   // Mode APPEND: afegeix al final

Molta gent ha perdut dades per no adonar-se d’aquest detall.

Aquest és el cicle de vida complet de treballar amb un fitxer:

flowchart LR
    A["Obrir<br/>(FileReader/Writer)"] --> B["Llegir o<br/>escriure"]
    B --> C["Tancar<br/>(automàtic amb<br/>try-with-resources)"]

ConsellMini-repte

Escriu un programa que demani noms per teclat (fins que l’usuari escrigui “fi”) i els vagi guardant, un per línia, en un fitxer noms.txt.

Després, torna a obrir el fitxer i mostra tots els noms per pantalla.

10.7 Crear i esborrar fitxers i directoris

Sovint cal preparar l’estructura de carpetes abans de guardar-hi res.

La classe Files ens ho facilita:

import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;

Path carpeta = Path.of("dades");
Path fitxer = Path.of("dades/registre.txt");

try {
    if (!Files.exists(carpeta)) {
        Files.createDirectories(carpeta);       // Crea la carpeta (i les intermèdies)
    }
    if (!Files.exists(fitxer)) {
        Files.createFile(fitxer);               // Crea el fitxer buit
    }

    Files.delete(fitxer);                       // Esborra el fitxer
} catch (IOException e) {
    System.out.println("Error d'operació de fitxers: " + e.getMessage());
}

Operacions més habituals amb Files:

Operació Mètode
Comprovar si existeix Files.exists(ruta)
Crear un fitxer buit Files.createFile(ruta)
Crear un directori (i pares) Files.createDirectories(ruta)
Esborrar Files.delete(ruta)
Copiar Files.copy(origen, desti)
Moure o reanomenar Files.move(origen, desti)
AlertaError típic: esborrar un directori que no és buit

Files.delete sobre un directori només funciona si està buit. Si conté fitxers, llançarà una excepció. Primer has de buidar-lo.

Esborrar és definitiu, no va a la paperera de reciclatge. Comprova sempre la ruta abans.

ConsellMini-repte

Escriu un programa que comprovi si existeix la carpeta sortides/; si no existeix, que la creï; i que després hi generi un fitxer informe.txt amb la data d’avui.

10.8 Resum

  • Java modela l’entrada/sortida com a fluxos o streams: corrents de dades ordenats entre un origen i un destí.
  • Hi ha fluxos de bytes (dades binàries: imatges, àudio) i de caràcters (text, respecta els accents).
  • Scanner llegeix del teclat; vigila el problema de nextInt() seguit de nextLine().
  • printf i DecimalFormat donen control sobre el format de la sortida.
  • File (clàssic) i Path (modern) representen rutes; a Windows, usa / o \\.
  • Per llegir text: BufferedReader + readLine() en bucle, o Files.readAllLines.
  • Per escriure text: PrintWriter/BufferedWriter; el segon argument true de FileWriter afegeix en comptes de sobreescriure.
  • El try-with-resources (try (...)) tanca els recursos automàticament: fes-lo servir sempre amb fitxers.
  • La classe Files permet crear, esborrar, copiar i moure fitxers i directoris.

10.9 Per practicar

  1. Agenda simple. Demana noms i telèfons per teclat i guarda’ls en un fitxer agenda.csv amb el format nom;telefon.
  2. Comptador de línies. Llegeix un fitxer de text i mostra quantes línies, quantes paraules i quants caràcters té.
  3. Factura formatada. Demana producte, preu i unitats, i escriu en un fitxer una línia ben alineada amb printf (imports amb 2 decimals i símbol €).
  4. Registre d’accessos. Cada cop que s’executi el programa, afegeix (mode append) una línia amb la data actual a log.txt.
  5. Còpia de seguretat. Fes un programa que copiï el contingut d’un fitxer a un altre línia a línia.
  6. Gestor de carpetes. Crea l’estructura projecte/dades/ i projecte/sortides/ si no existeix, i informa de què ha calgut crear.
  7. Mitjana des de fitxer. Llegeix un fitxer amb un nombre per línia i calcula’n la mitjana, gestionant amb try-catch les línies que no siguin nombres (Capítol 8).