# Accés aleatori a fitxers amb java #### Comparació fitxer seqüencial vs aleatori Un fitxer **seqüencial** té com a característica principal que els registres (la informació continguda en aquests) s'insereix de forma cronològica, de tal forma que un registre nou s'insereix després del darrer registre (a partir del final del fitxer). Així, com a pricipals avantatges tenim que s'accedeix ràpidament al darrer registre i la seva senzillesa d'ús. Però el seu principal desavantatge és no poder accedir a un registre en concret (de forma aleatòria), havent de passar abans per tots els anteriors registres. Totes les classes estudiades anteriorment, és a dir, FileReader i FileWriter, BufferedReader i BufferedWriter, etc. assumeixen accés seqüencial als fitxers. Un fitxer **aleatori** ens permet accedir a una zona concreta (posició) del fitxer ja que manipulen adreces relatives en compte d'adreces absolutes (és a dir pista i sector dins el disc on es troba el fitxer). Com avantatge tenim el ràpid accés a una posició determinada per llegir o escriure un registre. Però el gran inconvenient que té ve d'establir la relació entre la posició que ocupa el registre i el seu contingut. Com veurem caldrà aplicar alguna funció de conversió que sinó es crida correctament ens podrà retornar dades errònies. Java disposa de la classe **RandomAccessFile** per poder treballar l'accés aleatori a fitxers, és a dir, per poder accedir al contingut d'un fitxer binari de forma aleatòria i per posicionar-nos en una posició concreta del mateix. #### La classe RandomAccessFile La classe RandomAccessFile té dos constructors que poden llençar excepcions FileNotFoundException: * **RandomAccessFile (String nomfitxer, String modeAcces)**, on nomfitxer és el path (ruta) al fitxer. * **RandomAccessFile (File objecteFile, String modeAcces)**, on fem ús d'un objecte File que conté la ruta al fitxer. D'altra banda el paràmetre **modeAcces** pot ser 'r' (lectura) o 'rw' (lectura i escriptura). Un cop obert el fitxer farem servir els [mètodes de lectura i escriptura de les classes DataInputStream i DataOutputStream](https://hackmd.io/qC7hJrWhSceMAO-a1M4X9g#Fitxers-binaris), és a dir, readUTF/writeUTF, readInt/writeInt, etc. La classe RandomAccessFile maneja un punter que indica la posició actual al fitxer. Quan el fitxer es crea el punter es col·loca a l'inici del fitxer. Les successives crides a read() i write() desplaçaran el punter segons la quantitat de bytes llegits o escrits. Per últim, els mètodes més importants que disposem són: * long getFilePointer(): Retorna la posició actual del punter. * void seek (long posicio): Col·loca el punter en una posició determinada des del principi del mateix * void length (): Retorna la mida del fitxer en bytes. La posició length() marca el final del fitxer. * int skipBytes (int desplaçament): Desplaça el punter des de la posició actual el nombre de bytes indicats al paràmetre desplaçament. Anem a fer un exemple sobre com funciona i practicarem tot seguit amb algun exercici. #### Exemples d'aplicació, escriptura i lectura El següent codi escriu un fitxer fent servir RandomAccessFile, el qual conté informació de nom, departament i salari dels empleats de l'empresa. Totes aquestes dades estan disposades en forma d'arrays i les escriurem de forma similar a com escrivim els fitxers binaris seqüencials. Tingueu present que la longitud de registre serà fixa, de tal forma que: * El nom de l'empleat farem que sigui màxim 20 caràcters. Com Java utiliza **UTF-16**, cada caràcter és emmagatzemat fent servir blocs de 16 bits (és a dir, 2 bytes). Per tant, per 20 caràcters tindrem 20 * 2 = 40 bytes. * El departament que és un enter, seran 4 bytes. * El salari que es de tipus doble, seran 8 bytes. En total cada registre seran 40 + 4 + 8 = 52 bytes. El codi del programa serà: ``` import java.io.IOException; import java.io.RandomAccessFile; public class EscriureAleatori { public static void main(String[] args) throws IOException { RandomAccessFile raf1 = new RandomAccessFile("Empleats.dat", "rw"); String nomsEmpleats[] = {"Pere Gil","Josep Lopez","Marti Garrido"}; int departaments[] = {10,25,15}; Double salari[] = {1200.00,2300.25,750.32}; //Utilitzarem un StringBuffer per tractar l'array de noms d'empleats, el qual //inicialitzarem tot seguit: StringBuffer sbf = null; //Com els arrays tenen tots la mateixa mida, prenem l'array de //noms i cognoms per fer la iteració for (int i = 0; i<nomsEmpleats.length; i++) { sbf = new StringBuffer(nomsEmpleats[i]); //Longitud de caràcters màxima sbf.setLength(20); //No escrivim amb UTF i si caràcter a caràcter per assegurar //que escrivim 2 by per posició. raf1.writeChars(sbf.toString()); //Escrivim ara departament i salari raf1.writeInt(departaments[i]); raf1.writeDouble(salari[i]); } raf1.close(); } } ``` Fixeu-vos que **s'ha preferit utilitzar writeChars() en comptes de writeUTF()**. Una de les raons és que volem que els noms dels empleats sempre ocupin 20 posicions a cada registre, de forma fixa. Amb writeUTF l'escriptura de cadenes es dinàmica, de tal forma que les posicions que s'escriuen varien depenent del nombre de caràcters. Aleshores la única forma d'aconseguir una escriptura fixa i predecible és amb writeChars(), ja que ens assegurarà que si o si escriurà un caràcter fins assolir la longitud màxima del buffer. Ara anem a veure com llegir i mostrar per pantalla el contingut d'aquest fitxer. ``` import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; public class LlegirAleatori { public static void main(String[] args) throws FileNotFoundException, IOException { RandomAccessFile raf1 = new RandomAccessFile("Empleats.dat", "r"); //Declarem la variable que ens permetrà posicionar-nos al fitxer int posicio=0; //Declarem la variable auxiliar que emmagatzemarà cada caràcter //de l'array de caràcters del nom char aux; //Declarem les variables dels camps que anem a llegir char nomEmpleat[] = new char[20]; int departament; double salari; //condició d'iteració: fins a la longitud màxima de fitxer while(raf1.getFilePointer() != raf1.length()){ //ens posicionem d'acord al valor de la variable posició raf1.seek(posicio); //iterem caràcter a caràcter i els anem ficant a nomEmpleat for (int i=0;i<nomEmpleat.length;i++) { aux = raf1.readChar(); nomEmpleat[i] = aux; } //Reconstruïm en un únic String String nomCompletEmpleat = new String(nomEmpleat); departament = raf1.readInt(); salari = raf1.readDouble(); //Si el fitxer no està buit i s'ha llegit informació, fem el printf if (!(nomCompletEmpleat.isEmpty())) { System.out.printf("Nom empleat: %s, Departament: %d, Salari: %.2f %n", nomCompletEmpleat.trim(),departament,salari); //reposicionem per anar al següent registre posicio = posicio + 52; } } raf1.close(); } } ``` Fixeu-vos el paper que juga el mètode seek(posicio) i perquè és important conèixer la longitud de cada registre que escrivim. **Seek** serveix per posicionar on comencen cadascun dels registres que volem llegir. I si ens equivoquem en el valor que pren el paràmetre **posicio** que passem al seek, el codi ens donarà una excepció de tipus **java.io.EOFException**. L'output d'aquest fitxer segons executem l'anterior codi és: ``` Nom empleat: Pere Gil, Departament: 10, Salari: 1200,00 Nom empleat: Josep Lopez, Departament: 25, Salari: 2300,25 Nom empleat: Marti Garrido, Departament: 15, Salari: 750,32 ``` #### **Exercicis** Prenent com exemple els codis anteriors feu un petit programa que demani per pantalla el nom d'un empleat i faci les següents accions: * Mostrar totes les dades de l'empleat si aquest existeix (inclós el seu nom). * Modificar el seu departament dins l'arxiu "Empleats.dat". Per dur a terme aquesta acció caldrà que també es demani per pantalla quin és el nou departament a assignar. En cas que no es localitzi l'empleat dintre el fitxer es mostrarà un missatge per pantalla tipus "No hi ha empleats amb aquest nom". #### **Bibliografia consultada:** Alicia Ramos - Acceso a Datos - Garceta <p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/"><a property="dct:title" rel="cc:attributionURL" href="https://hackmd.io/@TTalens/SyllSXBk1x">Fitxers acces aleatori amb java</a> by <a rel="cc:attributionURL dct:creator" property="cc:attributionName" href="https://hackmd.io/@TTalens">Toni Talens</a> is licensed under <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1" alt=""></a></p>