Salta el contingut

UD11. Tractament de Fitxers

1. Introducció

Tots els llenguatges de programació tenen alguna forma d'interactuar amb els fitxers. Vegem les operacions que podem fer en un fitxer:

Consultar característiques del fitxer Llegir del fitxer Escriure en el fitxer
- Comprovar si existeix, si tenim permisos, si és un directori...
- Consultar data del fitxer, grandària...
1r. Obrir el fitxer
2n. Mentre resten dades per llegir Llegir dades del fitxer
3r. Tancar el fitxer
1r. Obrir el fitxer
2n. Mentre tenim dades per escriure Escriure dades al fitxer
3r. Tancar el fitxer

Moltes operacions sobre fitxers generen excepcions (per intentar llegir d'un fitxer inexistent, no tindre permisos, etc.). Per tant, abans de vore què podem fer amb els fitxers, veiem primer com tractar, en general, les excepcions.

1.1. Tractament de les excepcions

Veiem 2 formes de tractar les excepcions en general, amb un exemple de fitxers:

Ja veiérem que podem tractar les excepcions amb try-catch:

Java
public static void llegirFitxer() {
    ...
    try {
        // Ús de mètodes e lectura de fitxers que poden generar excepcions
    }
    catch (FileNotFoundException e) {
        System.out.println("Fitxer no trobat");
    }
    catch (IOException e) {
        System.out.println("Error accedint al fitxer " + e.getMessage());
    }
    ...
}

2. Consultar característiques d'un fitxer

En un programa en Java podrem saber coses com la grandària d'un fitxer, qui és el seu directori pare, si té permís de lectura, etc.

Per a fer això existeix la classe File. Per a saber les característiques d'un fitxer crearem un objecte d'eixa classe instanciant-lo amb el nom del fitxer. Després, accedirem a les propietats del fitxer amb els mètodes d'eixa classe.

  • 1r pas: associar al fitxer un objecte de la classe File (cal importar java.io.File);

Fitxers, pas 1

  • 2n pas: accedir a les propietats del fitxer usant els mètodes de la classe.

Fitxers, pas 2

També hi ha mètodes per a obtindre la llista dels fitxers que hi ha dins d'un directori. Si el fitxer que tenim en el File correspon a un directori, podem fer:

Java
1
2
3
File directori = new File("/Users/paco/Desktop");
String[] nomsFitxers = directori.list();
File[] fitxers = directori.listFiles();

Amb list() obtenim un array dels noms de fitxers que estan dins del directori. Però si volem accedir a les propietats de cadascun d'eixos fitxers, seria millor listFiles(), ja que ens retorna un array d'objectes de la classe File que estaran apuntant a cadascun d'eixos fitxers. Veiem-ho gràficament:

PROVA

Exercici 1. Consultar les característiques de fitxers

Fes la funció lligFitxer() que demane per teclat un nom de fitxer (o directori). Si existeix, retornarà el File que apunta al fitxer. Si no existeix, retornarà null.

Exercici 2

Fes la funció mostraAtributsFitxer(), a la qual se li passa com a paràmetre un fitxer (no nom de fitxer sinó objecte File). La funció mostrarà informació del fitxer com en l'exemple. Si el fitxer no existeix retornarà false (true en cas contrari).

Exemple exercici 2

La data la tindrem com un número long. Per a obtindre la data en format String podeu fer servir aquesta funció:

Java
1
2
3
public static String dataString(long data) {
    return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date(data));
}
Exercici 3

Fes la funció llistaFitxers(), a la qual se li passa com a paràmetre un directori (no el nom del directori sinó l'objecte File). Mostrarà les dades de cada fitxer (o subdirectori) del directori (usa la funció mostraAtributsFitxer()).

Exercici 4

Fes la funció llistaFitxersRecursivament(), qui rep com a paràmetre un directori (File). Per a cada fitxer o subdirectori seu, mostrarà les seues dades. A més, si és un subdirectori, mostrarà el seu contingut, cridant recursivament a la funció.

Exemple execució

3. Fitxers de text. Streams d'entrada

En un programa en Java potser ens interessarà llegir el contingut d'un fitxer de text on puga haver un escrit qualsevol, un document html, un fitxer de configuració de Linux...

A vegades ens interessarà llegir del fitxer caràcter a caràcter. En eixe cas usarem la classe FileReader. Però si volem llegir línia a línia usarem la classe BufferedReader.

A mode de resum, en aquesta imatge es veu com es relacionen les classes que podem usar per a llegir d'un fitxer de text:

Fitxers de Text

3.1. Lectura caràcter a caràcter

  • 1r pas: Associar al fitxer un objecte de la classe FileReader (i importar java.io.FileReader).

pas1

  • 2n pas: Llegir del filtxer.

    Executarem un read() per cada caràcter que volem llegir. Este mètode retorna un enter, que és el codi del caràcter llegit (o -1 si no hem pogut llegir del fitxer). Després cal fer casting a char per a treballar amb eixe caràcter.

    pas2

    NOTA: si tractem IOException, no cal tractar FileNotFoundException (ja que és filla) a no ser que vullgam missatges distints per a cada excepció.

    Exemple

    Llegim d'un fitxer de text i ho mostrem per pantalla:

    Java
    1
    2
    3
    4
    5
    6
    7
    public static void mostrarCaractersDeFitxer(String nomFitxer) throws IOException {
        FileReader fr = new FileReader(nomFitxer);
        while (fr.ready()) {
            char lletra = (char) fr.read();
            System.out.print(lletra);
        }
    }
    
Exercici 5. Lectura de fitxers de text amb FileReader (caràcter a caràcter)

Donat un fitxer de text fes un programa que indique:

Text Only
1
2
3
Quantes vocals hi ha al fitxer, quants espais en blanc, quantes majúscules i quantes minúscules.

Ampliació: compta també les paraules. Es consideraran separadors de paraules (a més de l'espai en blanc): . , ; : ! ?
Exercici 6. Corregir exàmens tipus test

En un examen de tipus test, cada alumne deixa en un fitxer amb el seu nom ("Pep.txt") les seues 20 respostes (que poden ser A, B, C, D o bé un guionet si no vol contestar-se una pregunta) en la primera línia del fitxer. Per exemple, un fitxer podria tindre esta línia: ABCD-BBA-CCDBACBC-DA.

En altre fitxer de text ("solucio.txt") tindrem la solució, que serà: ABCDDCBAACCDBAABCDDA.

Fes un programa tal que demane el nom del fitxer de l'alumne. El programa haurà de mostrar-nos quantes respostes ha encertat i quantes ha fallat. També la nota que ha tret l'alumne, tenint en compte que cada pregunta correcta suma 0.5 punts i cada pregunta incorrecta resta 0.125 punnts.

Modifica el programa per a que demane en bucle els noms dels fitxers de tots els alumnes fins que li posem de nom de fitxer "fi".

3.2. Lectura línia a línia

Si en compte de llegir d'un fitxer caràcter a caràcter volem fer-ho línia a línia, necessitarem un objecte de la classe BufferesReader, associat a un objecte de la classe FileReader, Caldrà importar: java.io.BufferedReader.

lectura linia a linia

  • Constructor i mètodes:
Java
1
2
3
4
5
BufferedReader br = new BufferedReader(fr);

br.ready(); // retorna true/false si queden línies per llegir

br.readLine(); // retorna un String amb la següent línia del fitxer

Exemple. Llegim d'un fitxer de text i ho mostrem per pantalla

Java
1
2
3
4
5
6
7
8
9
public static void mostrarLiniesDeFitxer(String nomFitxer) throws IOException {
    FileReader fr = new FileReader(nomFitxer);
    BufferedReader br = new BufferedReader(fr);
    String s;
    while (br.ready()) {
        s = br.readLine();
        System.out.println(s);
    }
}
Exercici 7. Lectura de fitxers de text amb BufferedReader (línia a línia)

Escriu un program que diga quants paràgrafs té un fitxer de text. Que mostre també una llista amb el número de paràgraf i la quantitat de lletres que té cadascun.

4. Fitxers de text. Streams d'eixida

Per a escriure en un fitxer de text tenim classes anàlogues a les de lectura:

  • FileWriter → escriptura caràcter a caràcter (o Strings).
  • BufferedWriter → escriptura línia a línia (afig newLine()).

4.1. Escriptura caràcter a caràcter

Java
1
2
3
4
FileWriter fw = new FileWriter("sortida.txt");
fw.write('H');
fw.write('i');
fw.close();

Atenció: per defecte, obrir un fitxer amb FileWriter és destructiu (sobreescriu el contingut anterior). Per a afegir al final del fitxer cal passar true com a segon argument:

Java
FileWriter fw = new FileWriter("sortida.txt", true); // mode append

4.2. Escriptura línia a línia

Combinem FileWriter amb BufferedWriter per poder usar newLine():

Java
1
2
3
4
5
6
7
8
FileWriter fw = new FileWriter("poesia.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("No hi havia a València dos amants com nosaltres.");
bw.newLine();
bw.write("Feroçment ens amàvem des del matí a la nit.");
bw.newLine();
bw.close();
fw.close();
Exercici 8. Escriptura de fitxers de text

Fes un programa que demane a l'usuari que introduïsca línies de text fins que escriga "fi". Guarda totes les línies en un fitxer de text anomenat sortida.txt.

Exercici 9. Còpia de fitxer

Fes un programa que copie el contingut d'un fitxer d'origen a un fitxer de destí, tots dos introduïts per teclat. Usa FileReader i FileWriter caràcter a caràcter.

5. Fitxers binaris

Les classes que intervenen al procés de lectura i escriptura de fitxers binaris són diferents a les de fitxers de text.

Acces aa dades binàries

Els fitxers binaris guarden les dades en la seua representació binària (enters, decimals, booleans...). Per accedir-hi usem:

  • FileInputStream / FileOutputStream → lectura/escriptura de bytes.
  • DataInputStream / DataOutputStream → lectura/escriptura de tipus bàsics (int, double, boolean...).
  • ObjectInputStream / ObjectOutputStream → lectura/escriptura d'objectes.
  • RandomAccessFile → accés aleatori a dades binàries, posicionant el cursor on vulguem.

L'accés és seqüencial: el cursor avança conforme llegim o escrivim i no pot tornar enrere.

Java
// Escriptura
FileOutputStream fos = new FileOutputStream("dades.bin");
DataOutputStream dos = new DataOutputStream(fos);
dos.writeInt(42);
dos.writeDouble(3.14);
dos.writeUTF("Hola");   // escriu la longitud (2 bytes) + el text
dos.close();

// Lectura (en el mateix ordre!)
FileInputStream fis = new FileInputStream("dades.bin");
DataInputStream dis = new DataInputStream(fis);
int n       = dis.readInt();
double d    = dis.readDouble();
String text = dis.readUTF();
dis.close();

Important: la lectura s'ha de fer en el mateix ordre que l'escriptura.

5.1. Accés aleatori

RandomAccessFile permet posicionar el cursor en qualsevol punt del fitxer (accés directe):

Java
RandomAccessFile raf = new RandomAccessFile("dades.bin", "rw"); // "r" o "rw"

raf.seek(0);              // posiciona el cursor a l'inici
long pos = raf.getFilePointer(); // posició actual del cursor
long mida = raf.length(); // grandària del fitxer en bytes
raf.skipBytes(4);         // salta 4 bytes

raf.writeInt(100);
int valor = raf.readInt();
raf.close();
Mètode Descripció
seek(long pos) Mou el cursor a la posició pos
getFilePointer() Retorna la posició actual del cursor
length() Grandària del fitxer en bytes
skipBytes(int n) Salta n bytes endavant
readTIPUS / writeTIPUS Llig/escriu un tipus bàsic
close() Tanca el fitxer
Exercici 10. Fitxers binaris

Fes un programa que guarde 10 enters aleatoris en un fitxer binari i després els llija i mostre per pantalla.

6. Fitxers d'objectes

Podem guardar objectes sencers en un fitxer. Per a això la classe ha d'implementar la interfície Serializable:

Java
1
2
3
4
5
class Alumne implements java.io.Serializable {
    String nom;
    int edat;
    // ...
}

Usarem ObjectOutputStream per escriure i ObjectInputStream per llegir:

Java
// Escriptura
FileOutputStream fos = new FileOutputStream("alumnes.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(new Alumne("Anna", 20));
oos.writeObject(new Alumne("Pere", 22));
oos.close();

// Lectura
FileInputStream fis = new FileInputStream("alumnes.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
while (fis.available() > 0) {
    Alumne a = (Alumne) ois.readObject();
    System.out.println(a.nom);
}
ois.close();

6.1. Afegir objectes: problemàtica

Obrir el fitxer en mode append i usar ObjectOutputStream normalment afig una capçalera extra al mig del fitxer, causant errors en la lectura posterior.

Guardant objectes

La solució és crear un ObjectOutputStream personalitzat propi que sobreescriga writeStreamHeader() perquè no escriga eixa capçalera:

Java
1
2
3
4
5
6
7
8
9
public class MiObjectOutputStream extends ObjectOutputStream {
    public MiObjectOutputStream(OutputStream out) throws IOException {
        super(out);
    }
    @Override
    protected void writeStreamHeader() throws IOException {
        // No escrivim capçalera
    }
}

Llavors: 1. Crear el fitxer → ObjectOutputStream (amb capçalera). 2. Afegir al fitxer → MiObjectOutputStream (sense capçalera).

6.2. Manera de treballar recomanada

Per a programes amb molt de volum de dades:

  1. En iniciar, carregar tots els objectes del fitxer a un ArrayList en memòria.
  2. Treballar sobre l'ArrayList (consultar, afegir, modificar, esborrar).
  3. En acabar, volcar l'ArrayList al fitxer.

Com que ArrayList és un objecte, es pot guardar sencer amb una sola crida:

Java
1
2
3
4
5
// Guardar tot l'ArrayList
oos.writeObject(llistaAlumnes);

// Recuperar tot l'ArrayList
ArrayList<Alumne> llistaAlumnes = (ArrayList<Alumne>) ois.readObject();
Exercici 11. Fitxers d'objectes

Crea una classe Producte (nom, preu, stock) que siga serialitzable. Fes un programa que permeta afegir productes a un fitxer i llistar-los tots.