Mòdul 5

Pràctica 5: Operacions d'entrada i sortida amb fitxers    
Tornar presentació tema
    Pràctica 1 Pràctica 2 Pràctica 3 Pràctica 4 Pràctica 4  
 

L'accés a fitxers escrits en un disc és una de les operacions més convencionals que ha de saber resoldre un programador. Quan escriguis programes necessitaràs llegir i escriure paràmetre de configuració de l'aplicació, buscar dades desades sistemàticament, llegir fonts de dades enregistrades en format xml, navegar entre les carpetes d'un disc dur i moltes accions més que impliquen la necessitat en el llenguatge de programació d'un bon sistema de gestió de fitxers.

Java té moltes formes d'accedir i treballar amb fitxers. Tantes que a vegades l'aprenent de programador es pot confondre en la seva utilització.

En aquesta pràctica aprendràs a treballar amb algunes de les eines de gestió de fitxers que ens facilita Java. No seran totes, però si tindràs els suficients elements com per a solucionar la major part de necessitats de treball amb fitxers d'una aplicació convencional.

 
     
  La classe File  
 
En primer lloc estudiaràs la classe de Java que ens permet obtenir informació sobre els fitxers i carpetes que hi ha en un dispositiu d'emmagatzemament físic. Aquesta feina la fa la classe File, una classe molt senzilla d'aprendre i força pràctica d'utilitzar.

Els objectes File proporcionen algunes utilitats relacionades amb fitxers i serveixen per a llegir informació bàsica sobre aquest fitxers. Pots verificar si el fitxer existeix, la seva longitut, els seus atributs i un bon grapat de coses més.

File pot analitzar fitxers i carpetes. Les dues coses són considerats el mateix tipus d'objecte. Aquesta característica facilita molt la gestió i anàlisi de carpetes d'un disc dur.

Ara aprendràs a utilitzar la classe File a través d'un petit programa que et donarà la descripció dels fitxers que hi ha dins d'una carpeta del teu disc dur.

 
     
Obre el BlueJ i crea un nou projecte amb el nom de 'fitxers'. En aquest projecte escriuràs les classes d'aquesta pràctica. Crea la classe Descripcions i dóna-li el següent contingut:
 
     
import java.io.*;

/**
* Llista i dóna les característiques dels fitxers d'una carpeta
* @author Angel Solans
* @version 25-02-2005
*/

public class Descripcions {
    public void getDescripcio(String dir) throws IOException {
        File carpeta = new File(dir);
        if (!carpeta.isDirectory())
           throw new IOException(
              "El nom no correspon a una carpeta");
        String[] llistaFitxers = carpeta.list();
        for (int n=0;n<llistaFitxers.length;n++) {
            File fitxer = new File(carpeta,llistaFitxers[n]);
            System.out.println("Nom: "+fitxer.getName());
            System.out.println("Camí: "+fitxer.getPath());
            System.out.println("Lectura: "+fitxer.canRead());
            System.out.println("Escriptura: "+fitxer.canWrite());
            System.out.println("bytes: "+fitxer.length());
        }
    }
}

 
     
 

Aquest programa ens retornarà una llista amb la descripció de tots els fitxers que hi ha a l'interior de la carpeta triada.

Compil·la el programa i comprova el seu funcionament. Crea una instància de Descripcions i crida el mètode getDescripcio(). Observaràs que, seguint el que has après a la pràctica anterior, si escrius com a paràmetre una carpeta inexistent, el programa genera una excepció i es trenca.

El programa crea un objecte File a partir del paràmetre dir que has introduït:

        File carpeta = new File(dir);

Seguidament reculls la llista de fitxers de la carpeta en una matriu:

        String[] llistafitxers = carpeta.list();

Finalment, per a cada element de la llista crees un nou objecte File i en fas sortir a cònsola algunes de les seves característiques:

        for (int n=0;n<llistaFitxers.length;n++) {
            File fitxer = new File(carpeta,llistaFitxers[n]);
            System.out.println("Nom: "+fitxer.getName());
            System.out.println("Camí: "+fitxer.getPath());
            System.out.println("Lectura: "+fitxer.canRead());
            System.out.println("Escriptura: "+fitxer.canWrite());
            System.out.println("bytes: "+fitxer.length());
        }

 
     
Investiga sobre la classe File. Busca més mètodes que facilitin informació sobre els fitxers i afegeix-los al programa que acabes d'escriure. Aquí et deixem el nom d'alguns d'aquests mètodes:

String getName()
String getPath()
String getAbsolutePath()
String getParent()
boolean renameTo( File nuevoNombre )
boolean exists()
boolean canWrite()
boolean canRead()
boolean isFile()
boolean isDirectory()
boolean isAbsolute()
long lastModified()
long length()
 
     
  Llegir d'un fitxer de text, línia a línia  
 


La classe File és útil per a obtenir informació sobre els fitxers i carpetes, però no ens permet accedir al contingut dels fitxers. Per a fer aquesta feina Java disposa d'un grapat de classes que llegeixen i escriuen fitxers seguint metodologies diferents.

Per a llegir el contingut de fitxers plans de text, els més utilitzats per a desar volumns petits d'informació, utilitzaràs objectes de la classe BufferedReader. Aquesta classe, entre altres utilitats, és capaç de llegir fitxers de text línia a línia tot carregant el contingut de la línia en un objecte String. El programador pot així processar el contingut de cadascuna de les línies del fitxer.

Posa-ho en pràctica escrivint aquesta petita aplicació. Obre el BlueJ i afegeix al projecte fitxers aquesta classe. Crea la Classe LlegirLinies i Compil·la les fonts:

 
     
import java.io.*;
/**
*
* @author Angel Solans
* @version 25-02-2005
*/

public class LlegirLinies {
    public void llegeix(String fitxer) throws IOException {
        try {
            BufferedReader in =
                new BufferedReader(new FileReader( fitxer ));
            String linia;
            while ((linia = in.readLine()) != null) {
                System.out.println(linia);
            }
            in.close();
        } catch (IOException e) { }
    }
}
 
     
 

Crea una instància del programa LlegirLinies i crida el mètode llegeix(). Com a paràmetre pots donar un fitxer de text present en el teu disc dur, c:/archivos de programa/bluej/readme.txt, per exemple.

Observaràs que el programa t'imprimirà a cònsola el contingut del fitxer. La forma d'operar és bastant senzilla: crees un objecte BufferedReader que carrega en memòria el contingut del fitxer. La classe BufferedReader sap llegir des de diferents dispositius. Per a dir-li que llegeixi d'un fitxer, creem la classe a partir d'una instància de FileReader, una classe especialitzada en llegir fitxers:

            BufferedReader in =
                new BufferedReader(new FileReader( fitxer ));

BufferedReader té un mètode que es diu readLine() que va llegint seqüencialment les línies del fitxer: llegeix un línia i es situa a la línia següent. Quan no queda cap línia al final del fitxer, el mètode readLine() retorna un null:

            while ((linia = in.readLine()) != null) {
                System.out.println(linia);
            }

Finalment, tanques el BufferedReader per alliberar els recursos.             in.close();

Tot el procés queda protegit per un try{ que captura errors del tipus IOException, els que es generen quan falla una lectura de fitxer.

 
     
  Escriure en un fitxer de text  
     
 


L'escriptura d'un fitxer és el procés invers al que acabes de fer. Es codifica i processa d'una forma similar.

Obre el projecte 'fitxers' i afegeix aquest petit programa:

 
     
import java.io.*;

/**
*
* @author Angel Solans
* @version 25-02-2005
*/

public class EscriureFitxer {
    public void escriu(String fitxer) throws IOException {
    try {
        BufferedWriter out =
             new BufferedWriter(new FileWriter( fitxer ));
        out.write( "Escric aquest text en un fitxer" );
        out.close();
    } catch (IOException e) { }
    }
}

 
     
 

Compil·la i crea una instància del programa. Executa el mètode escriu() i passa-li com a paràmere el nom complet del fitxer que vols escriure. Per exemple, "c:/untext.txt". Navega fins la carpeta de destinació del fitxer i veuràs com el programa t'ha escrit un fitxer 'untext.txt' amb el contingut de "Escric aquest text en un fitxer".

Aquest mètode d'escriptura és agressiu: si no existeix el fitxer el crea, si existeix, el substitueix. Una segona versió del programa serà més respectuosa amb els continguts del fitxer preexistent:

 
     
import java.io.*;

/**
*
* @author Angel Solans
* @version 25-02-2005
*/

public class EscriureFitxerActualitzacio {
    public void escriu(String fitxer) throws IOException {
    try {
        BufferedWriter out =
             new BufferedWriter(new FileWriter( fitxer ,true));
        out.write( "Escric aquest text en un fitxer"+\n );
        out.close();
    } catch (IOException e) { }
    }
}

 
     
 

A l'obrir el BufferedWriter amb el nou constructor, li demanem al FileWriter que no esborri el fitxer, cas d'existir i que n'afegeixi el nou contingut.

 
     
Fitxers properties.  
     
 

Molts programes desen elements de configuració en fitxers de text simple que el programa llegeix quan ho necessita. A l' entorn Windows són freqüents els fitxers *.ini . Java disposa d'un equivalent als fitxers *.ini, els fitxers *.properties .

Un fitxer properties és una llista de parells de valors. El contingut d'un fitxer d'aquest tipus té un aspecte com el següent:

ciutat=Lleida
tipus=VISA
codi=4893498283

Cada línia conté una parella de valors. El primer correspon al nom de la propietat, el segon al seu contingut. Aquests valors es poden incorporar amb molta facilitat als nostres programes i convertir-los, per exemple, en variables.

Crea ara un fitxer de text amb la llibreta del windows amb el següent contingut:

usuari=Marc Pérez
nick=marcus
idioma=ca

Desa-la amb el nom c:/configuracio.properties.

Ara escriu, en el projecte fitxers, el programa 'Propietats' que llegirà i gestionarà les propietats:

 
import java.util.Properties;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
*
* @author Angel Solans
* @version 25-02-2005
*/

public class Propietats {
    static Properties props;

    static void getPropietats()
        throws FileNotFoundException, IOException {
        props=new Properties();
        FileInputStream in = new FileInputStream(
            "c:/configuracio.properties");
        props.load(in);
        String usuari=props.getProperty("usuari");
        String nick=props.getProperty("nick");
        String idioma=props.getProperty("idioma");
        System.out.println("Benvingut jugador: "+usuari);
        System.out.println("nick: "+nick);
        System.out.println("idioma: "+idioma);
    }

    static void canviIdioma(String idioma)
        throws FileNotFoundException, IOException {
        getPropietats();
        OutputStream output =
            new FileOutputStream("c:/configuracio.properties");
        props.setProperty("idioma",idioma);
        props.store(output, "Propietats de l'usuari");
        output.flush();
        output.close();
        System.out.println("canvi d'idioma, nou idioma: "+idioma);
    }
}

 
     
 

Un cop tinguis escrita la classe, compil·la-la. Per a executar els mètodes getPropietats() i canviIdioma() no és necessari que creis una instància de la classe: es tracta de mètodes static. En el BlueJ, pica amb el ratolí el botó dret sobre la caixa de la classe Propietats: podràs executar els mètodes static de la classe. Endevines perquè els mètodes static s'executen des de la classe i no des dels objectes de la classe?

El mètode getPropietats() llegeix el fitxer configuracio.properties i en carrega el contingut a les variables usuari,nick i idioma. La lectura es fa a partir d'un objecte FileInputStream, una classe especialitzada en la lectura de fitxers en forma de raig de bytes.

        FileInputStream in = new FileInputStream(
            "c:/configuracio.properties");

Seguidament, carreguem les propietats a l'objecte Properties gràcies al mètode load().

        props.load(in);

Finalment llegim el valor de les propietats.

        String usuari=props.getProperty("usuari");
        String nick=props.getProperty("nick");
        String idioma=props.getProperty("idioma");

A la inversa, a través del mètode canviIdioma(). podem reescriure la propietat idioma del fitxer. Ho fem a través d'un FileOutputStream, una classe especialitzada en l'escriptura de fitxers.

Utilitzem la propietat store() de la classe Properties per a enregistrar les dades al fitxer.

 
     
1) Afegeix un nou mètode a la darrera classe que faci possible actualitzar el nick del nostre usuari al fitxer configuracio.properties.  
     
Fitxers de lectura i escriptura byte a byte.  
     
 

A l'exemple anterior has utilitzat la classe FileInputStream. L'hem definida com una classe que fa una lectura de fitxers a raig, és a dir, byte darrera byte: llegeix el primer byte del fitxer, després el següent i així successivament fins a trovar-se amb el marcador de final de fitxer.

La lectura/escriptura de fitxers seguint la metodologia d'accés byte a byte et dóna el control total sobre els seus continguts. Poden ser fitxers de text, imatges, videos, recursos Flash, qualsevol cosa que pugui ser escrita byte a byte.

Els dos programes següents il·lustren la metodologia de treball amb aquest tipus de fitxers.

Escriuràs, en primer lloc, un programa que fa el recompte del número de vocals 'a' que hi ha en un fitxer de text. Té el següent aspecte:

 
     
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
*
* @author Angel Solans
* @version 25-02-2005
*/

public class ComptadorVocalsA {

    public static String getBytesFromFile(String file)
        throws IOException {
        File fitxer = new File(file);
        InputStream is = new FileInputStream(fitxer);
        long length = file.length();
        if (length > Integer.MAX_VALUE)
            throw new IOException("Fitxer massa llarg");
        int comptador = 0; // Comptador de lletres 'a'
        int n = 0;
        try {
            while (n!=-1) { // -1 indica el final del fitxer
                n=is.read();
                if (n==97) comptador+=1; //Si el byte és una 'a'
            }
        }catch(Exception e){}
        is.close();
        return "Aquest text té "+comptador+" lletres a";
    }
}

 
     
 

Creem un InputStream que recull el caudal de bytes del fitxer i anem llegint byte a byte el contingut del fitxer fins a rebre l'indicador de final de fitxer, que és l'enter -1. Per a cada byte, verifiquem si es tracta d'una a (té el valor enter de 97) i actualitzem el comptador. Senzill!!.

El camí invers, escriure un fitxer byte a byte es pot resoldre de la següent forma. El programa següent, enregistra una frase:

 
     
import java.io.*; /**
*
* @author Angel Solans
* @version 25-02-2005
*/

class EscripturaByteaByte {
    static FileOutputStream fos;
    public static void enregistra() throws IOException {
        fos = new FileOutputStream( "c:/dades.txt" );
        String frase ="Enregistro aquesta frase en raig de bytes";
        byte[] bytes = frase.getBytes();
        for( int n=0; n<bytes.length; n++ )
             fos.write( bytes[n] );
        fos.close();
    }
}

 
     
 

En lloc d'un InputStream utilitzes un FileOutputStream que crea un flux de bytes cap a un fitxer. Prenem una cadena i la convertim en una matriu de bytes:

        byte[] bytes = frase.getBytes();

Finalment, a través d'un bucle for fem l'escritura al fitxer byte a byte amb el mètode write() de l'objecte FileOutputStream. A la fi, tanquem aquest objecte per a passar-lo al gestor d'escombreries amb una crida al mètode close().

 
     
  Fitxers d'accés aleatori  
     
 

Hi ha ocasions en que l'accés a un fitxer de forma seqüencial és una tasca força pesada. Imagina un fitxer enormement gran al que, de tant en tant, has d'anar afegint informació. Si en fas un tractament seqüencial, cada vegada que intentis afegir informació, el programa recorrerà tot el contingut del fitxer abans d'arribar al final i afegir el que t'interessa.

Si en fas un accés aleatori, afegir informació a un fitxer és més senzill: pots saltar fins als final i escriure immediatament.

Experimenta el concepte amb un darrer exemple. Escriuràs un programa la funció del qual és anar afegint observacions de temperatura a un fitxer. Com el programa pot estar funcionant molts dies sense parar, el fitxer amb les dades pot ser molt gran. Li hem de fer un accés aleatori:

 
     
 

/**
*
* @author Angel Solans
* @version 25-02-2005
*/

import java.io.*;

class RegistreTemperatures {
    public static void enregistra(String data, String valor)
         throws IOException {
        RandomAccessFile fitxer;
        fitxer = new RandomAccessFile("c:/temperatures.log", "rw");
        fitxer.seek(fitxer.length()); // saltem al final
        fitxer.writeBytes( data+";"+valor+"\n" );
        fitxer.close();
    }
}

 
     
  I això és pràcticament tot el que necessites saber sobre fitxers per a començar. Es tracta, però, de la punta de l'iceberg perquè Java té moltes formes de llegir i escriure dades. No insistirem més aquí, les aniràs descobrint a mesura que les necessitis.  
 

 

Tornar al principi