M3 - Programació estructurada / Continguts UF3: Fitxers orientats a byte

De wikiserver
Dreceres ràpides: navegació, cerca

Fitxers orientats a byte

Accés següèncial

Les dades s’emmagatzemen com una seqüència de valors, i per tant, l’esquema general que s'aplica en fitxers orientats a caràcter també es pot aplicar a aquest altre tipus.

Representació de les dades en format binari.

Tipus Paura clau Java Mide (bytes)
caràcter char 2
byte byte 1
enter curt short 2
enter simple int 4
enter llarg long 8
real de simple precisió float 4
real de doble precisió doble 8

Inicialització

import java.io.File;
import java.io.RandomAccessFile;   // s’usa tant per llegir com per escriure dades
RandomAccessFile raf = new RandomAccessFile(File ruta, String mode);  
                                  // File ruta per especificar la ruta del fitxer
                                  // String mode per especificar el mode de treball
                                  // a l'hora de processar el fitxer

Dels modes de treball possibles el dos més utilitzats són:

  • r: mode lectura. Errors si el fitxer no existeix o si s'invoca a mètodes d'escriptura.
  • rw: mode escriptura-lectura. Si el fitxer no existeix, es crearà un de nou buit. Si existeix, no s’esborren les seves dades.

Escriptura de dades

L’única manera de generar fitxers orientats a byte que puguin ser llegits correctament és mitjançant codi d’un programa.

Cada cop que es fa una operació de lectura o escriptura, l’apuntador es desplaça automàticament el mateix nombre de bytes amb què s’ha operat.

Mètodes disponibles per escriure dades:

Mètode Bytes escrits
writeBoolean(boolean b) 1
writeByte (byte v) 1
writeChar(char c) 2
writeDouble(double d) 8
writeFloat(float f) 4
writeInt(int i) 4
writeLong(long l) 8
writeShort(short s) 2

Exemple d'escriptura de 20 enters:

public class EscriureEntersBinari {

      public static void main(String[] args) throws IOException {

        EscriureEntersBinari programa = new EscriureEntersBinari();
        programa.inici();
     }

     public void inici() throws FileNotFoundException, IOException {

       File f = new File("Enters.bin");
       RandomAccessFile raf = new RandomAccessFile(f, "rw");
       int valor = 1;
       for (int i = 0; i < 20; i++) {
          raf.writeInt(valor);
          valor = valor * 2;
       }
       System.out.println("Fitxer escrit satisfactòriament.");
       raf.close();
     }
   }

Sobreescriptura de fitxers

La classe RandomAccessFile quan escriu en un fitxer, no esborra les dades existents, per la qual cosa pot quedar "brossa" al final del fitxer. En molts casos caldrà eliminar els bytes sobrants un cop acabada l'escriptura.

Mètodes emprats:

  • setLength(long mida). Modifica la mida del fitxer. Si el valor especificat és més petit que la mida actual, s’eliminen totes les dades per sobre de la mida especificada. Si és més gran, el contingut extra és indefinit.
  • long getFilePointer(). Avalua la posició on és en aquests moments l’apuntador, mesurat en el nombre de bytes des de l’inici del fitxer.
  • long length(). Retorna la mida del fitxer en bytes.

Exemple: reemplaç del contingut delfitxer “enters.bin” (20 valors enters), per cinc valors enters -1. El resultat final és un fitxer de només 20 bytes, en lloc de 80.

public class SobreescriureEntersBinari {

      public static void main(String[] args) throws IOException {

        SobreescriureEntersBinari programa = new SobreescriureEntersBinari();
        programa.inici();
      }

      public void inici() throws FileNotFoundException, IOException {
        
        File f = new File("Enters.bin");
        RandomAccessFile raf = new RandomAccessFile(f, "rw");
        long apuntador = raf.getFilePointer();
        System.out.println("Inici: Apuntador a posició " + apuntador);
        for (int i = 0; i < 5; i++) {
          raf.writeInt(-1);
        }
        apuntador = raf.getFilePointer();
        System.out.println("Fi: Apuntador a posició " + apuntador);
        raf.setLength(apuntador);
        raf.close();
        System.out.println("Fitxer modificat correctament.");
     }
   }

Lectura de dades

hi ha un mètode específic per a cada tipus de dades:

Mètode Tipus de dada llegida
readByte() byte
readShort() short
readInt() int
readLong() long
readFloat() float
readDouble() double
readBoolean() boolean
readChar() char

L'intent de llegir més enllà del darrer valor provocarà una excepció. Es pot calcular quants valors d’un tipus conté el fitxer fent una simple divisió: mida fitxer/mida tipus.

Exemple: lectura del fitxer "Enters.bin".

public class LlegirEntersBinari {

      public static void main(String[] args) throws IOException {

        LlegirEntersBinari programa = new LlegirEntersBinari();
        programa.inici();
      }

      public void inici() throws IOException {
 
        File f = new File("Enters.bin");
        RandomAccessFile raf = new RandomAccessFile(f, "r");
        long numEnters = f.length() / 4;
        System.out.println("Hi ha " + numEnters + " enters.");
        for (int i = 0; i < numEnters; i++) {
          int valor = raf.readInt();
          System.out.println("S’ha llegit el valor " + valor);
        }
        raf.close();
      }
    }

Lectura incorrecta de dades

La classe RandomAccessFile quan executa qualsevol dels seus mètode de lectura, sempre llegeix el nombre de bytes associats al tipus de dada que es vol llegir, i els interpreta al valor que correspon dins aquest tipus. El programa no ens avisarà en cap excepció.

Arxius d’accés directe

S’anomena accés directe a la capacitat d’accedir a qualsevol element dins una seqüència de dades sense haver de tractar prèviament els elements anteriors.

La classe RandomAccessFile permet l’accés directe als arxius orientats a byte.

Posicionament

Cada byte individual dins del fitxer és com una posició d’un array, i el primer byte del fitxer es consideraria la seva posició 0.

Els mètodes de lectura i escriptura de la classe RandomAccessFile no disposen de cap paràmetre on s’especifiqui un índex. Disposa d'un apuntador intern que s'actualitza automàticament en cada operació i indica, en cada moment, quina és la següent dada a processar.

Per gestionar el posicionament de l’apuntador, la classe defineix els mètodes següents:

  • void seek (long pos). Ubica l’apuntador exactament a la posició especificada pel paràmetre pos, mesurat en bytes. No hi ha cap restricció en el valor d’aquest paràmetre.
  • int skipBytes (int n). Avança la posició de l’apuntador en n bytes. Si l’apuntador arriba al final del fitxer, el desplaçament de l’apuntador s’atura. Si n és negatiu no es mou.

Lectura directa de dades

Exemple: Llegir i mostrar valors d'un fitxer d'enters (Enters.bin). En concret, es mostraren les posicions alternatives a partir de la meitat del fitxer (posicions 11, 13, 15, etc.).

public class LlegirEntersAleatori {

      public static void main(String[] args) throws IOException {

        LlegirEntersAleatori programa = new LlegirEntersAleatori();
        programa.inici();
      }

      public void inici() throws IOException {
    
        File f = new File("enters.bin");
        RandomAccessFile raf = new RandomAccessFile(f, "r");
        long numEnters = f.length() / 4;
        long meitat = numEnters / 2;
        raf.seek(meitat * 4);
        long pos = raf.getFilePointer();
        while (pos < f.length()) {
          System.out.print("apuntador a la posició " + pos + "-->");
          int valor = raf.readInt();
          System.out.println(" S’ha llegit el valor " + valor);
          raf.skipBytes(4);
          pos = raf.getFilePointer();
        }
      }
    }

Escriptura directa de dades

La classe RandomAccessFile permet modificar el valor on s’ubica l’apuntador i cap altre.

Exemple: Modifica les posicions múltiples de 5 per la pròpia posició (reemplaça la posició 5 per un 5, la 10 per un 10, ...) en el fitxer "Enters.bin". En acabar l’escriptura, es mostren els nous valors del fitxer per pantalla.

public class EscriureEntersAleatori {

     public static void main(String[] args) throws IOException {

       EscriureEntersAleatori programa = new EscriureEntersAleatori();
       programa.inici();
     }

     public void inici() throws IOException {

       File f = new File("Enters.bin");
       System.out.println("Valors inicials del fitxer.");
       mostrarFitxerBinari(f);
       modificaFitxerBinari(f);
       System.out.println("Nous valors del fitxer.");
       mostrarFitxerBinari(f);
     }  

     public void mostrarFitxerBinari(File f) throws IOException {

       RandomAccessFile raf = new RandomAccessFile(f, "r");
       long pos = raf.getFilePointer();
       while (pos < f.length()) {
         int valor = raf.readInt();
         System.out.print(" " + valor);
         pos = raf.getFilePointer();
       }
       raf.close();
       System.out.println();
     }

     public void modificaFitxerBinari(File f) throws IOException {
      
       RandomAccessFile raf = new RandomAccessFile(f, "rw");
       raf.seek(16);
       long pos = raf.getFilePointer();
       int i = 1;
       while (pos < f.length()) {
         raf.writeInt(i * 5);
         i++;
         raf.skipBytes(4 * 4);
         pos = raf.getFilePointer();
       }
       raf.close();
     }
   }

Seek més enllà de la mida del fitxer

És un cas especial, que es produeix quan l’apuntador s’ubica més enllà del final del fitxer (per exemple, el fitxer té 80 bytes i s’ubica a la posició 100), i llavors es duu a terme una escriptura. En aquest cas, la mida del fitxer s’incrementa automàticament fins a la posició de l’apuntador, omplint tots els nous bytes amb 0. Un cop fet, llavors es materialitza l’escriptura.

Per exemple, si donat un fitxer de 80 bytes s’usa el mètode seek per ubicar l’apuntador fins a la posició 100 i llavors es fa l’escriptura d’un enter, el fitxer creixerà fins als 100 bytes, i els seus darrers 20 bytes estaran a zero. Llavors es durà a terme l’escriptura, de manera que la mida del fitxer final queda en 104 bytes. El programa següent mostra aquest cas.

public class SeekCreixFitxerBinari {

     public static final String NOM_FITXER = "Seek.bin";

     public static void main(String[] args) throws IOException {

       SeekCreixFitxerBinari programa = new SeekCreixFitxerBinari();
       programa.inici();
     }

     public void inici() throws IOException {
     
       File f = new File(NOM_FITXER);
       crearFitxer(f);
       System.out.println("La mida inicial és " + f.length());
       executaSeek(f);
       System.out.println("La mida a l’hora de fer seek i escriure és " + f.length());
     }

     public void crearFitxer(File f) throws IOException {

       RandomAccessFile raf = new RandomAccessFile(f, "rw");
       for (int i = 0; i < 20; i++) {
         raf.writeInt(i);
       }
       raf.setLength(20 * 4);
       raf.close();
     }

     public void executaSeek(File f) throws IOException {

       RandomAccessFile raf = new RandomAccessFile(f, "rw");
       raf.seek(f.length() + 20);
       raf.writeInt(100);
       raf.close();
     }
   }