Temas:
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.
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.
El siguiente programa resuelve el problema de la motivación:
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();
Suponiendo que el archivo post.raf está ordenado por
carnet, la función buscar puede realizar una búsqueda binaria:
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();
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;
}
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));
}
}