Archivos de Acceso Directo

Objetivos: Mostrar que los archivos también se pueden accesar en forma no secuencial y que esto es útil para no tener que visitar completamente archivos de gran tamaño.

Temas:


Archivos de acceso directo

En muchos problemas de manejo de información, se debe reunir la información que aparece en dos archivos. Como se dijo en el capítulo anterior, el pareo de ambos archivos ordenados según un mismo campo es una solución eficiente. Sin embargo existe un caso en donde esta solución no es la mejor: cuando uno de los archivos es mucho más pequeño que el otro.

Cuando uno de los archivos contiene un número reducido de líneas resulta más eficiente emplear archivos de acceso directo, porque permiten reunir la información sin tener que visitar el archivo completo.

Motivación:

Para ejemplificar esta situación, consideraremos el archivo de postulantes del capítulo anterior. El problema consistirá en obtener los nombres de unos 100 postulantes de los cuales sólo se tiene el carnet de identidad en el archivo carnets.dat. El archivo de postulantes es lo suficientemente grande como para que su contenido no quepa en la memoria del computador.

Definiciones:

Un archivo se accesa secuencialmente cuando el programa que lo utiliza ya sea lo lee o lo escribe en forma secuencial. Hasta el momento sólo hemos visto programas que accesan secuencialmente los archivos.

Un archivo se accesa directamente cuando el programa que lo utiliza especifica directamente la posición dentro del archivo que se accesará. Por ejemplo el programa puede leer la línea número 200, luego la línea 50000 y más tarde la línea 1.

Es importante notar que para el sistema operativo todos los archivos pueden ser accesados directamente. Sin embargo, es muy difícil accesar directamente los archivos de texto que hemos usado hasta el momento, porque sus líneas tienen largo variable.

Para que un archivo se pueda accesar cómodamente en forma directa, es necesario que sus líneas tengan un número fijo de caracteres. Por esta razón, los archivos de acceso directo que usaremos en los ejemplos, tendrán la extensión ".raf". Lamentablemente, estos archivos no pueden ser vistos mediante WordPad o NotePad porque su contenido no es necesariamente desplegable en pantalla.


Búsqueda binaria en un archivo de acceso directo

Para resolver el problema que nos motiva, supondremos que existe una clase PostRAF que permite hacer accesos directos en archivos de postulantes con la extensión ".raf". Más tarde implementaremos esta clase.

Ejemplo Significado Declaración
PostRAF praf= new PostRAF("post.raf"); Construye un lector/escritor de acceso directo para post.raf PostRAF(String arch)
Post post= praf.leer(30); Entrega el postulante número 30 Post leer(int nro)
praf.escribir(50, post2); Escribe el postulante post2 en la posición 50 void escribir(int nro, Post post)
int tamaño= praf.tamaño(); Entrega el número de postulantes en el archivo int tamaño
praf.close(); Cierra el archivo void close()

Ejemplo de uso: convertir el archivo post.dat al archivo de acceso directo post.raf.

    TextReader lect= new TextReader("post.dat", ":");
    PostRAF postRaf= new PostRAF("post.raf");
    int i= 0;
    while (!lect.eofReached()) {
      Post post= new Post(lect);
      postRaf.escribir(i, post);
      i++;
    }
    println("leidos y escritos: "+i);
    lect.close();
    postRaf.close();
El siguiente programa resuelve el problema de la motivación:

    PostRAF postRaf= new PostRAF("post.raf");
    TextReader lect= new TextReader("carnets.dat", ":");
    TextWriter escr= new TextWriter("nombres.dat", ":");
    while (!lect.eofReached()) {
      String ci= lect.readString();
      Post post= buscar(ci, postRaf);
      if (post!=null)
        post.escribir(escr);
      else
        println("no se encontro: "+ci);
    }
    postRaf.close();
    lect.close();
    escr.close();
Suponiendo que el archivo post.raf está ordenado por carnet, la función buscar puede realizar una búsqueda binaria:

    Post buscar(String ci, PostRAF postRaf) {
      int imin= 0;
      int imax= postRaf.tamaño()-1;
      while (imin<=imax) {
        int icentro= (imin+imax)/2;
        Post post= postRaf.leer(icentro);
        int comp= compare(ci, post.ci)
        if (comp==0)
          return post;
        else if (comp<0)
          imax= icentro-1;
        else
          imin= icentro+1;
      }
      return null;
    }

La clase RandomAccessFile

La clase RandomAccessFile permite accesar archivos en forma directa. Esta clase permite ver un archivo como un arreglo de caracteres y leer o escribir zonas del arreglo. Como lo indica la siguiente figura, cada caracter se subindica mediante un índice.

Existe un puntero único que indica la posición del archivo que se accesará en la próxima operación de lectura o escritura.

La siguiente tabla resume los métodos principales de la clase:

Ejemplo Significado Declaración
RandomAccessFile raf= new RandomAccessFile("arch.raf", "r"); Construye un lector para arch.raf. RandomAccessFile(String nom, String modo)
RandomAccessFile raf2= new RandomAccessFile("arch.raf", "rw"); Construye un lector/escritor para arch.raf. RandomAccessFile(String nom, String modo)
raf2.writeUTF("hola"); Escribe "hola" en formato UTF a partir de la posición actual void writeUTF(String s)
String s= raf1.readUTF(); Lee un string en formato UTF a partir de la posición actual String readUTF()
raf1.seek(30); Cambia la posición actual del puntero al índice 30 void seek(int pos)
int largo= raf1.length(); Entrega el tamaño actual del archivo int length()
raf.close(); Cierra el archivo void close()

La operación writeUTF escribe un string agregando dos caracteres extras en donde se codifica el largo del string. Además se avanza el puntero de modo que la próxima escritura no altere lo que se acaba de escribir:

Del mismo modo, la operación readUTF determina el largo del string que se va a leer a partir de los dos primeros bytes (desde la posición actual):

Con estas operaciones definimos la clase PostRAF de la siguiente manera:

    class PostRAF extends Program {
      RandomAccessFile raf;
      int largo;
      int largoci= 10; // 10 caracteres para el ci
      int largonombre= 30; // 30 para el nombre
      PostRAF(String arch) {
        this.raf= new RandomAccessFile(arch, "rw");
        this.largo= 2+this.largoci+ 2+this.largonombre;
      }
      Post leer(int nro) {
        this.raf.seek(nro*this.largo);
        String ci= trim(this.raf.readUTF());
        String nombre= trim(this.raf.readUTF());
        return new Post(ci, nombre);
      }
      void escribir(int nro, Post post) {
        this.raf.seek(nro*largo);
        this.raf.writeUTF(extender(post.ci, this.largoci));
        this.raf.writeUTF(extender(post.nombre, this.largonombre));
      }
      int tamaño() {
        return raf.length()/largo;
      }
      void close() {
        raf.close();
      }
      String extender(String s, int l) {
        return s+repeat(" ", l-length(s));
      }
    }