Professional Documents
Culture Documents
AlexisJurez
Ing.EmirGirn
Ing.GiovanniRuano
1 Generalidades:
Muy a menudo necesitamos almacenar cierta cantidad de datos de forma ms o menos
permanente. La memoria del ordenador es volatil, y lo que es peor, escasa y cara. De
modo que cuando tenemos que guardar nuestros datos durante cierto tiempo tenemos que
recurrir a sistemas de almacenamiento ms econmicos, aunque sea a costa de que sean
ms lentos.
Durante la historia de los ordenadores se han usado varios mtodos distintos para el
almacenamiento de datos. Al principio se recurri a cintas de papel perforadas, despus a
tarjetas perforadas. A continuacin se pas al soporte magntico, empezando por grandes
rollos de cintas magnticas abiertas.
Hasta aqu, todos los sistemas de almacenamiento externo eran secuenciales, es decir, no
permitan acceder al punto exacto donde se guardaba la informacin sin antes haber
partido desde el principio y sin haber ledo toda la informacin, hasta el punto donde se
encontraba la que estabamos buscando.
Con las cintas magnticas empez lo que con el tiempo sera el acceso aleatorio a los
datos. Se poda reservar parte de la cinta para guardar cierta informacin sobre la
situacin de los datos, y aadir ciertas marcas que hicieran ms sencillo localizarla.
Pero no fu hasta la aparicin de los discos magnticos cuando sta tcnica lleg a su
sentido ms amplio. En los discos es ms sencillo acceder a cualquier punto de la
superficie en poco tiempo, ya que se accede al punto de lectura y escritura usando dos
coordenadas fsicas. Por una parte la cabeza de lectura/escritura se puede mover en el
sentido del radio del disco, y por otra el disco gira permanentemente, con lo que
cualquier punto del disco pasa por la cabeza en un tiempo relativamente corto. Esto no
pasa con las cintas, donde slo hay una coordenada fsica.
Con la invencin y proliferacin de los discos se desarrollaron los ficheros de acceso
aleatorio, que permiten acceder a cualquier dato almacenado en un fichero en
relativamente poco tiempo.
Actualmente, los discos duros tienen una enorme capacidad y son muy rpidos, aunque
an siguen siendo lentos, en comparacin con las memorias RAM. El caso de los CD es
algo intermedio. En realidad son secuenciales en cuanto al modo de guardar los datos,
cada disco slo tiene una pista de datos grabada en espiral. Sin embargo, este sistema,
combinado con algo de memoria RAM, proporciona un acceso muy prximo al de los
discos duros.
En cuanto al tipo de acceso, en C y C++ podemos clasificar los archivos segn varias
categoras:
En cuanto a cmo se definen estas propiedades, hay dos casos. Si son binarios o de texto
o de entrada, salida o entrada/salida, se define al abrir el fichero, mediante la funcin
fopen en C o mediante el mtodo open de fstream en C++.
La funcin open usa dos parmetros. El primero es el nombre del fichero que contiene el
archivo. El segundo es em modo que es una cadena que indica el modo en que se abrir
el archivo: lectura o escritura, y el tipo de datos que contiene: de texto o binarios.
En C, los ficheros admiten seis modos en cuanto a la direccin del flujo de datos:
En cuanto a los valores permitidos para los bytes, se puede aadir otro carcter a la
cadena de modo:
En ciertos sistemas operativos no existe esta distincin, y todos los ficheros son binarios.
En C++ es algo diferente, el constructor de las clases ifstream, ofstream y fstream admite
los parmetros para abrir el fichero directamente, y tambin disponemos del mtodo
open, para poder crear el stream sin asociarlo con un fichero concreto y hacer esa
asociacin ms tarde.
Funcin fopen:
Sintaxis:
FILE *fopen(char *nombre, char *modo);
sta funcin sirve para abrir y crear ficheros en disco. El valor de retorno es un puntero a una estructura FILE. Los
parmetros de entrada son:
1. nombre: una cadena que contiene un nombre de fichero vlido, esto depende del sistema operativo que
estemos usando. El nombre puede incluir el camino completo.
2. modo: especifica en tipo de fichero que se abrir o se crear y el tipo de datos que puede contener, de texto
o binarios:
r: slo lectura. El fichero debe existir.
w: se abre para escritura, se crea un fichero nuevo o se sobreescribe si ya existe.
a: aadir, se abre para escritura, el cursor se situa al final del fichero. Si el fichero no existe, se crea.
r+: lectura y escritura. El fichero debe existir.
w+: lectura y escritura, se crea un fichero nuevo o se sobreescribe si ya existe.
a+: aadir, lectura y escritura, el cursor se situa al final del fichero. Si el fichero no existe, se crea.
t: tipo texto, si no se especifica "t" ni "b", se asume por defecto que es "t"
b: tipo binario.
Funcin fclose:
Sintaxis:
int fclose(FILE *fichero);
Es importante cerrar los ficheros abiertos antes de abandonar la aplicacin. Esta funcin sirve para eso. Cerrar un
fichero almacena los datos que an estn en el buffer de memoria, y actualiza algunos datos de la cabecera del
fichero que mantiene el sistema operativo. Adems permite que otros programas puedan abrir el fichero para su
uso. Muy a menudo, los ficheros no pueden ser compartidos por varios programas.
Un valor de retorno cero indica que el fichero ha sido correctamente cerrado, si ha habido algn error, el valor de
retorno es la constante EOF. El parmetro es un puntero a la estructura FILE del fichero que queremos cerrar.
Funcin fgetc:
Sintaxis:
int fgetc(FILE *fichero);
Funcin fputc:
Sintaxis:
int fputc(int caracter, FILE *fichero);
Funcin feof:
Sintaxis:
int feof(FILE *fichero);
Esta funcin sirve para comprobar si se ha alcanzado el final del fichero. Muy frecuentemente deberemos trabajar
con todos los valores almacenados en un archivo de forma secuencial, la forma que suelen tener los bucles para
leer todos los datos de un archivo es permanecer leyendo mientras no se detecte el fin de fichero. Esta funcin
suele usarse como prueba para verificar si se ha alcanzado o no ese punto.
El valor de retorno es distinto de cero slo si no se ha alcanzado el fin de fichero. El parmetro es un puntero a la
estructura FILE del fichero que queremos verificar.
Funcin rewind:
Sintaxis:
void rewind(FILE *fichero)
Es una funcin heredada de los tiempos de las cintas magnticas. Literalmente significa "rebobinar", y hace
referencia a que para volver al principio de un archivo almacenado en cinta, haba que rebobinarla. Eso es lo que
hace sta funcin, sita el cursor de lectura/escritura al principio del archivo.
Funcin fgets:
Sintaxis:
char *fgets(char *cadena, int n, FILE *fichero);
Esta funcin est diseada para leer cadenas de caracteres. Leer hasta n-1 caracteres o hasta que lea un retorno de
lnea. En este ltimo caso, el carcter de retorno de lnea tambin es ledo.
El parmetro n nos permite limitar la lectura para evitar derbordar el espacio disponible en la cadena.
El valor de retorno es un puntero a la cadena leda, si se ley con xito, y es NULL si se detecta el final del
fichero o si hay un error. Los parmetros son: la cadena a leer, el nmero de caracteres mximo a leer y un
puntero a una estructura FILE del fichero del que se leer.
Funcin fputs:
Sintaxis:
int fputs(const char *cadena, FILE *stream);
La funcin fputs escribe una cadena en un fichero. No se aade el carcter de retorno de lnea ni el carcter nulo
final.
El valor de retorno es un nmero no negativo o EOF en caso de error. Los parmetros de entrada son la cadena a
escribir y un puntero a la estructura FILE del fichero donde se realizar la escritura.
Funcin fread:
Sintaxis:
Esta funcin est pensada para trabajar con registros de longitud constante. Es capaz de leer desde un fichero uno
o varios registros de la misma longitud y a partir de una direccin de memoria determinada. El usuario es
responsable de asegurarse de que hay espacio suficiente para contener la informacin leda.
El valor de retorno es el nmero de registros ledos, no el nmero de bytes. Los parmetros son: un puntero a la
zona de memoria donde se almacenarn los datos ledos, el tamao de cada registro, el nmero de registros a leer
y un puntero a la estructura FILE del fichero del que se har la lectura.
Funcin fwrite:
Sintaxis:
size_t fwrite(void *puntero, size_t tamao, size_t nregistros, FILE
*fichero);
Esta funcin tambin est pensada para trabajar con registros de longitud constante y forma pareja con fread. Es
capaz de escribir hacia un fichero uno o varios registros de la misma longitud almacenados a partir de una
direccin de memoria determinada.
El valor de retorno es el nmero de registros escritos, no el nmero de bytes. Los parmetros son: un puntero a
la zona de memoria donde se almacenarn los datos ledos, el tamao de cada registro, el nmero de registros a
leer y un puntero a la estructura FILE del fichero del que se har la lectura.
Ejemplo:
// copia.c: Copia de ficheros
// Uso: copia <fichero_origen> <fichero_destino>
#include <stdio.h>
int main(int argc, char **argv) {
FILE *fe, *fs;
unsigned char buffer[2048]; // Buffer de 2 Kbytes
int bytesLeidos;
if(argc != 3) {
printf("Usar: copia <fichero_origen> <fichero_destino>\n");
return 1;
}
// Abrir el fichero de entrada en lectura y binario
fe = fopen(argv[1], "rb");
if(!fe) {
printf("El fichero %s no existe o no puede ser abierto.\n", argv[1]);
return 1;
}
// Crear o sobreescribir el fichero de salida en binario
fs = fopen(argv[2], "wb");
if(!fs) {
printf("El fichero %s no puede ser creado.\n", argv[2]);
fclose(fe);
return 1;
}
// Bucle de copia:
while((bytesLeidos = fread(buffer, 1, 2048, fe)))
fwrite(buffer, 1, bytesLeidos, fs);
// Cerrar ficheros:
fclose(fe);
fclose(fs);
return 0;
}
Funcin fprintf:
Sintaxis:
int fprintf(FILE *fichero, const char *formato, ...);
La funcin fprintf funciona igual que printf en cuanto a parmetros, pero la salida se dirige a un fichero en lugar
de a la pantalla.
Funcin fscanf:
Sintaxis:
int fscanf(FILE *fichero, const char *formato, ...);
La funcin fscanf funciona igual que scanf en cuanto a parmetros, pero la entrada se toma de un fichero en lugar
del teclado.
Funcin fflush:
Sintaxis:
int fflush(FILE *fichero);
Esta funcin fuerza la salida de los datos acumulados en el buffer de salida del fichero. Para mejorar las
prestaciones del manejo de ficheros se utilizan buffers, almacenes temporales de datos en memoria, las
operaciones de salida se hacen a travs del buffer, y slo cuando el buffer se llena se realiza la escritura en el
disco y se vaca el buffer. En ocasiones nos hace falta vaciar ese buffer de un modo manual, para eso sirve sta
funcin.
El valor de retorno es cero si la funcin se ejecut con xito, y EOF si hubo algn error. El parmetro de entrada
es un puntero a la estructura FILE del fichero del que se quiere vaciar el buffer. Si es NULL se har el vaciado de
todos los ficheros abiertos.
Funcin fseek:
Sintaxis:
int fseek(FILE *fichero, long int desplazamiento, int origen);
Esta funcin sirve para situar el cursor del fichero para leer o escribir en el lugar deseado.
El valor de retorno es cero si la funcin tuvo xito, y un valor distinto de cero si hubo algn error.
Los parmetros de entrada son: un puntero a una estructura FILE del fichero en el que queremos cambiar el cursor
de lectura/escritura, el valor del desplazamiento y el punto de origen desde el que se calcular el desplazamiento.
El parmetro origen puede tener tres posibles valores:
1. SEEK_SET el desplazamiento se cuenta desde el principio del fichero. El primer byte del fichero tiene un
desplazamiento cero.
2. SEEK_CUR el desplazamiento se cuenta desde la posicin actual del cursor.
3. SEEK_END el desplazamiento se cuenta desde el final del fichero.
Funcin ftell:
Sintaxis:
long int ftell(FILE *fichero);
La funcin ftell sirve para averiguar la posicin actual del cursor de lectura/excritura de un fichero.
El valor de retorno ser esa posicin, o -1 si hay algn error.
El parmetro de entrada es un puntero a una estructura FILE del fichero del que queremos leer la posicin del
cursor de lectura/escritura.
Clase ifstream:
El constructor est sobrecargado para poder crear streams de varias maneras:
ifstream();
ifstream(const char *name, int mode = ios::in,
int = filebuf::openprot);
El primero slo crea un stream de entrada pero no lo asocia a ningn fichero. El segundo lo crea, lo asocia al
fichero con el nombre "name" y lo abre.
Los parmetros son: el nombre del fichero, el modo, que para ifstream es ios::in por defecto. El tercer parmetro
se refiere al buffer, y no nos preocupa de momento.
Clase ofstream:
Lo mismo pasa con ofstream, salvo que los valores por defecto de los parmetros son diferentes:
ofstream();
ofstream(const char *name, int mode = ios::out,
int = filebuf::openprot);
Clase fstream:
fstream();
fstream(const char *name, int mode = ios::in,
int = filebuf::openprot);
Mtodo open:
Todas estas clases disponen adems del mtodo "open", para abrir el fichero a lo largo de la ejecucin del
programa.
void open(const char *name, int mode,
int prot=filebuf::openprot);
"name" es el nombre del fichero, mode es el modo en que se abrir, puede ser uno o una combinacin del tipo
enumerado open_mode, de la clase "ios":
enum open_mode { in, out, ate, app, trunc, nocreate,
noreplace, binary };
Cada uno de los valores se pueden combinar usando el operador de bits OR (|), y significan lo siguiente:
Los tres ltimos modos probablemente no son estndar, y es posible que no existan en muchos compiladores.
Mtodo close:
void close();
Operador >>:
Igual que sucede con el stream estndar cout, el operador de flujo de salida >> se puede usar con streams de salida
cuando trabajemos con texto.
Operador <<:
Del mismo modo, al igual que sucede con el stream estndar cin, el operador de flujo de entrada << se puede usar
con streams de entrada cuando trabajemos con texto.
La primera forma no se recomienda y se considera obsoleta, lee un carcter desde el stream de entrada.
La segunda lee caracteres y los almacena en el buffer indicado en el primer parmetro hasta que se leen "len"
caracteres o hasta que se encuentra el carcter indicado en el tercer parmetro, que por defecto es el retorno de
lnea.
La tercera forma extrae un nico carcter en la referencia a char proporcionada.
La cuarta no nos interesa de momento.
Extrae caracteres hasta que se encuentra el delimitador y los coloca en el buffer, elimina el delimitador del stream
de entrada y no lo aade al buffer.
Mtodo eof:
int eof();
Mtodo clear:
void clear(iostate state=0);
Cada vez que se produzca una condicin de error en un stream es necesario eliminarla, ya que en caso contrario
ninguna operacin que se realice sobre l tendr xisto. Por ejemplo, si llegamos hasta el final de fichero, el
stream quedar en estado "eof" hasta que se elimine explcitamente ese estado. Eso se hace mediante el mtodo
"clear", sin parmetros dejar el estado en 0, es decir, sin errores.
Los estados posibles se definen en un enumerado:
enum io_state { goodbit, eofbit, failbit, badbit };
Mtodo bad:
int bad();
Mtodo fail:
int fail();
Mtodo good:
int good();
int main() {
ifstream fichero("ejemplo1.cpp");
char c;
while(fichero.get(c)) cout.put(c);
fichero.clear(); // (1)
fichero.seekg(0);
while(fichero.get(c)) cout.put(c);
fichero.close();
cin.get();
return 0;
}
Como vemos en (1), es necesario eliminar el bit de eof, que se ha activado al leer hasta el final del fichero, cuando
el ltimo intento de llamar a "get" ha fallado, porque se ha terminado el fichero.
Mtodo is_open:
int is_open();
Mtodo flush:
ostream& flush();
Realiza las operaciones de escritura pendientes que an se han realizado slo en el buffer.
Mtodo seekg:
Cambia la posicin del cursor en streams de entrada.
istream& seekg(streampos pos);
istream& seekg(streamoff offset, seek_dir dir);
La primera forma es para cambiar la posicin de modo absoluto. La segunda para cambios relativos, en la que se
indica el salto en el primer parmetro y el punto de partida en el segundo, que puede ser cualquiera de los
indicados anteriormente: ios::beg, ios::cur o ios::end.
Mtodo seekp:
Cambia la posicin del cursor en streams de salida.
ostream& seekp(streampos pos);
ostream& seekp(streamoff offset, seek_dir);
Mtodo tellg:
streampos tellg();
Mtodo tellp:
streampos tellp();
Mtodo read:
istream& read(char*, int);
Lee el nmero de caracteres indicado en el segundo parmetro dendro del buffer suministrado por el primero.
Mtodo gcount:
int gcount();
Devuelve el nmero de caracteres sin formato de la ltima lectura. Las lecturas sin formato son las realizadas
mediante las funciones get, getline y read.
Mtodo write:
ostream& write(const char*, int);
Escribe el nmero de caracteres indicado en el segundo parmetro desde el buffer suministrado por el primero.
Ejemplo:
De nuevo haremos el ejemplo de copiar ficheros, pero esta vez usando streams.
// copia.cpp: Copia de ficheros
// Uso: copia <fichero_origen> <fichero_destino>
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char **argv) {
ifstream entrada;
ofstream salida;
char buffer[2048]; // Buffer de 2 Kbytes
int bytesLeidos;
if(argc != 3) {
printf("Usar: copia <fichero_origen> <fichero_destino>\n");
return 1;
}
// Abrir el fichero de entrada en lectura y binario
entrada.open(argv[1]);
if(!entrada.good()) {
printf("El fichero %s no existe o no puede ser abierto.\n", argv[1]);
return 1;
}
// Crear o sobreescribir el fichero de salida en binario
salida.open(argv[2]);
if(!salida.good()) {
printf("El fichero %s no puede ser creado.\n", argv[2]);
entrada.close();
return 1;
}
// Bucle de copia:
do {
entrada.read(buffer, 2048);
bytesLeidos = entrada.gcount();
salida.write(buffer, bytesLeidos);
} while(bytesLeidos > 0);
// Cerrar ficheros:
entrada.close();
salida.close();
return 0;
}
3 Archivos secuenciales
En estos archivos, la informacin slo puede leerse y escribirse empezando desde el
principio del archivo.
Los archivos secuenciales tienen algunas caractersticas que hay que tener en cuenta:
1. La escritura de nuevos datos siempre se hace al final del archivo.
2. Para leer una zona concreta del archivo hay que avanzar siempre, si la zona est
antes de la zona actual de lectura, ser necesario "rebobinar" el archivo.
3. Los ficheros slo se pueden abrir para lectura o para escritura, nunca de los dos
modos a la vez.
Esto es en teora, por supuesto, en realidad C no distingue si los archivos que usamos son
secuenciales o no, es el tratamiento que hagamos de ellos lo que los clasifica como de
uno u otro tipo.
Pero hay archivos que se comportan siempre como secuenciales, por ejemplo los
ficheros de entrada y salida estndar: stdin, stdout, stderr y stdaux.
Tomemos el caso de stdin, que suele ser el teclado. Nuestro programa slo podr abrir
ese fichero como de lectura, y slo podr leer los caracteres a medida que estn
disponibles, y en el mismo orden en que fueron tecleados.
Lo mismo se aplica para stdout y stderr, que es la pantalla, en estos casos slo se pueden
usar para escritura, y el orden en que se muestra la informacin es el mismo en que se
enva.
Un caso especial es stdaux, que suele ser el puerto serie. Tambin es un archivo
secuencial, con respecto al modo en que se leen y escriben los datos. Sin embargo se un
fichero de entrada y salida.
Trabajar con archivos secuenciales tiene algunos inconvenientes. Por ejemplo, imagina
que tienes un archivo de este tipo en una cinta magntica. Por las caractersticas fsicas
de este soporte, es eviente que slo podemos tener un fichero abierto en cada unidad de
cinta. Cada fichero puede ser ledo, y tambin sobrescrito, pero en general, los archivos
que haya a continuacin del que escribimos se perdern, o bien sern sobreescritos al
crecer el archivo, o quedar un espacio vaco entre el final del archivo y el principio del
siguiente.
Lo normal cuando se quera actualizar el contenido de un archivo de cinta aadiendo o
modificando datos, era abrir el archivo en modo lectura en una unidad de cinta, y crear
un nuevo fichero de escritura en una unidad de cinta distinta. Los datos ledos de una
cinta se editan o modifican, y se copian en la otra secuencialmente.
Cuando trabajemos con archivos secuenciales en disco haremos lo mismo, pero en ese
caso no necesitamos dos unidades de disco, ya que en los discos es posible abrir varios
archivos simultaneamente.
En cuanto a las ventajas, los archivos secuenciales son ms sencillos de manejar, ya que
requieren menos funciones, adems son ms rpidos, ya que no permiten moverse a lo
largo del archivo, el punto de lectura y escritura est siempre determinado.
En ocasiones pueden ser tiles, por ejemplo, cuando slo se quiere almacenar cierta
informacin a medida que se recibe, y no interesa analizarla en el momento.
Posteriormente, otro programa puede leer esa informacin desde el principio y
analizarla. Este es el caso de archivos "log" o "diarios" por ejemplo, los servidores de las
pginas WEB pueden generar una lnea de texto cada vez que alguien accede al una de
las pginas y las guardan en un fichero secuencial.
Teniendo en cuenta que los registros empiezan a contarse desde el cero, para hacer una lectura del registro
nmero 6 usaremos:
fseek(fichero, 5*sizeof(stRegistro), SEEK_SET);
fread(®, sizeof(stRegistro), 1, fichero);
Muy importante: despus de cada operacin de lectura o escritura, el cursor del fichero se actualiza
automticamente a la siguiente posicin, as que es buena idea hacer siempre un fseek antes de un fread o un
fwrite.
En el caso de streams, la forma de trabajar es anloga:
fichero.seekg(5*sizeof(stRegistro), ios::beg);
fichero.read(®, sizeof(stRegistro));
En el caso de streams:
long nRegistros;
long nBytes;
fichero.seekg(0, ios::end); // Colocar el cursor al final del fichero
nBytes = fichero.tellg(); // Tamao en bytes
nRegistros = fichero.tellg()/sizeof(stRegistro); // Tamao en registros
Borrar registros
Borrar registros puede ser complicado, ya que no hay ninguna funcin de librera estndar que lo haga.
Es su lugar se suele usar uno de estos dos mtodos:
1. Marcar el registro como borrado o no vlido, para ello hay que aadir un campo extra en la estructura del
registro:
struct stRegistro {
char Valido; // Campo que indica si el registro es vlido
char Nombre[34];
int dato;
int matriz[23];
};
Si el campo Valido tiene un valor prefijado, por ejemplo 'S' o ' ', el registro es vlido. Si tiene un valor
prefijado, por ejemplo 'N' o '*', el registro ser invlido o se considerar borrado.
De este modo, para borrar un registro slo tienes que cambiar el valor de ese campo.
Pero hay que tener en cuenta que ser el programa el encargado de tratar los registros del modo adecuado
dependiendo del valor del campo Valido, el hecho de marcar un registro no lo borra fsicamente.
Si se quiere elaborar ms, se puede mantener un fichero auxiliar con la lista de los registros borrados.
Esto tiene un doble propsito:
Que se pueda disear una funcin para sustituir a fseek() de modo que se tengan en cuenta los
registros marcados.
Que al insertar nuevos registros, se puedan sobrescribir los anteriormente marcados como
borrados, si existe alguno.
2. Hacer una copia del fichero en otro fichero, pero sin copiar el registro que se quiere borrar. Este sistema
es ms tedioso y lento, y requiere cerrar el fichero y borrarlo o renombrarlo, antes de poder usar de nuevo
la versin con el registro eliminado.
Lo normal es hacer una combinacin de ambos, durante la ejecucin normal del programa se borran registros
con el mtodo de marcarlos, y cuando se cierra la aplicacin, o se detecta que el porcentaje de registros borrados
es alto o el usuario as lo decide, se "empaqueta" el fichero usando el segundo mtodo.
Ejemplo:
A continuacin se incluye un ejemplo de un programa que trabaja con registros de acceso aleatorio, es un poco
largo, pero bastante completo:
// alea.c: Ejemplo de ficheros de acceso aleatorio.
#include <stdio.h>
#include <stdlib.h>
struct stRegistro {
char valido; // Campo que indica si el registro es vlido S->Vlido, N>Invlido
char nombre[34];
int dato[4];
};
int Menu();
void Leer(stRegistro ®);
void Mostrar(stRegistro ®);
void Listar(long n, stRegistro ®);
long LeeNumero();
void Empaquetar(FILE *fa);
int main()
{
stRegistro reg;
FILE *fa;
int opcion;
long numero;
fa = fopen("alea.dat", "r+b");
escribir
if(!fa) fa = fopen("alea.dat", "w+b");
crea.
do {
opcion = Menu();
switch(opcion) {
case '1': // Aadir registro
Leer(reg);
// Insertar al final:
fseek(fa, 0, SEEK_END);
fwrite(®, sizeof(stRegistro), 1, fa);
break;
case '2': // Mostrar registro
system("cls");
printf("Mostrar registro: ");
numero = LeeNumero();
fseek(fa, numero*sizeof(stRegistro), SEEK_SET);
system("cls");
printf("Leer registro:\n\n");
reg.valido = 'S';
printf("Nombre: ");
fgets(reg.nombre, 34, stdin);
// la funcin fgets captura el retorno de lnea, hay que eliminarlo:
for(i = strlen(reg.nombre)-1; i && reg.nombre[i] < ' '; i--)
reg.nombre[i] = 0;
for(i = 0; i < 4; i++) {
printf("Dato[%1d]: ", i);
fgets(numero, 6, stdin);
reg.dato[i] = atoi(numero);
}
}
// Muestra un registro en pantalla, si no est marcado como borrado
void Mostrar(stRegistro ®)
{
int i;
system("cls");
if(reg.valido == 'S') {
printf("Nombre: %s\n", reg.nombre);
for(i = 0; i < 4; i++) printf("Dato[%1d]: %d\n", i, reg.dato[i]);
}
system("PAUSE");
}
// Muestra un registro por pantalla en forma de listado,
// si no est marcado como borrado
void Listar(long n, stRegistro ®)
{
int i;
if(reg.valido == 'S') {
printf("[%6ld] %-34s", n, reg.nombre);
for(i = 0; i < 4; i++) printf(", %4d", reg.dato[i]);
printf("\n");
}
}
// Lee un nmero suministrado por el usuario
long LeeNumero()
{
char numero[6];
fgets(numero, 6, stdin);
return atoi(numero);
}
// Elimina los registros marcados como borrados
void Empaquetar(FILE *fa)
{
FILE *ftemp;
stRegistro reg;
ftemp = fopen("alea.tmp", "wb");
rewind(fa);
seekg(0, ios::end);
write(reinterpret_cast<char *> (®), sizeof(Registro));
cout << reg.Nombre() << endl;
}
bool Datos::Recupera(long n, Registro ®) {
clear();
seekg(n*sizeof(Registro), ios::beg);
read(reinterpret_cast<char *> (®), sizeof(Registro));
return gcount() > 0;
}
// Marca el registro como borrado:
void Datos::Borrar(long n) {
char marca;
clear();
marca = 'N';
seekg(n*sizeof(Registro), ios::beg);
write(&marca, 1);
}
// Elimina los registros marcados como borrados
void Datos::Empaquetar() {
ofstream ftemp("alea.tmp", ios::out);
Registro reg;
clear();
seekg(0, ios::beg);
do {
read(reinterpret_cast<char *> (®), sizeof(Registro));
cout << reg.Nombre() << endl;
if(gcount() > 0 && reg.Valido())
ftemp.write(reinterpret_cast<char *> (®), sizeof(Registro));
} while (gcount() > 0);
ftemp.close();
close();
remove("alea.bak");
rename("alea.dat", "alea.bak");
rename("alea.tmp", "alea.dat");
open("alea.dat", ios::in | ios::out | ios::binary);
}
int main()
{
Registro reg;
Datos datos;
int opcion;
long numero;
do {
opcion = Menu();
switch(opcion) {
case '1': // Aadir registro
reg.Leer();
datos.Guardar(reg);
break;
case '2': // Mostrar registro
system("cls");
cout << "Mostrar registro: ";
numero = LeeNumero();
if(datos.Recupera(numero, reg))
reg.Mostrar();
break;
case '3': // Eliminar registro
system("cls");
cout << "Eliminar registro: ";
numero = LeeNumero();
datos.Borrar(numero);
break;
case '4': // Mostrar todo
numero = 0;
system("cls");
cout << "Nombre
Datos" << endl;
while(datos.Recupera(numero, reg)) reg.Listar(numero++);
cout << "pulsa return";
cin.get();
break;
}
} while(opcion != '0');
return 0;
}
// Muestra un men con las opciones disponibles y captura una opcin del
usuario
int Menu()
{
char resp[20];
do {
system("cls");
cout << "MENU PRINCIPAL" << endl;
cout << "--------------" << endl << endl;
cout << "1- Insertar registro" << endl;
cout << "2- Mostrar registro" << endl;
cout << "3- Eliminar registro" << endl;
cout << "4- Mostrar todo" << endl;
cout << "0- Salir" << endl;
cin.getline(resp, 20);
} while(resp[0] < '0' && resp[0] > '4');
return resp[0];
}
// Lee un nmero suministrado por el usuario
long LeeNumero()
{
char numero[6];
fgets(numero, 6, stdin);
return atoi(numero);
}
[5, 8, 10]
[7]
Ahora sigue una pasada de mezcla, mezclaremos un tramo de cada fichero auxiliar en un nico tramo:
mezcla: [1, 2, 3, 4, 6, 9], [5, 7, 8, 10]
Y de mezclndolos de nuevo:
mezcla: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
El fichero ya est ordenado, para verificarlo contaremos los tramos obtenidos despus de cada proceso de mezcla,
el fichero estar desordenado si nos encontramos ms de un tramo.
Ejemplo:
// mezcla.c : Ordenamiento de archivos secuenciales
// Ordena ficheros de texto por orden alfabtico de lneas
// Usando el algoritmo de mezcla natural
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void
void
void
bool
Mostrar(FILE *fich);
Mezcla(FILE *fich);
Separar(FILE *fich, FILE **aux);
Mezclar(FILE *fich, FILE **aux);
int main()
{
FILE *fichero;
fichero = fopen("mezcla.txt", "r+");
puts("Fichero desordenado\n");
Mostrar(fichero);
puts("Ordenando fichero\n");
Mezcla(fichero);
puts("Fichero ordenado\n");
Mostrar(fichero);
fclose(fichero);
system("PAUSE");
return 0;
}
// Muestra el contenido del fichero "fich"
void Mostrar(FILE *fich)
{
char linea[128];
rewind(fich);
fgets(linea, 128, fich);
while(!feof(fich)) {
puts(linea);
fgets(linea, 128, fich);
}
}
// Algoritmo de mezcla:
void Mezcla(FILE *fich)
{
bool ordenado;
FILE *aux[2];
Ordenar archivos es siempre una tarea muy lenta y requiere mucho tiempo. Este algoritmo, adems requiere el
doble de espacio en disco del que ocupa el fichero a ordenar, por ejemplo, para ordenar un fichero de 500 megas
se necesitan otros 500 megas de disco libres.
Sin embargo, un fichero como el mencionado, sera muy difcil de ordenar en memoria.
Algoritmo Quicksort
Por supuesto, hay que elegir un algoritmo que impleque un mnimo de lecturas y escrituras en el fichero, y
preferentemente, que stas operaciones estn los ms prximas posible entre si. Resulta muy costoso, en
trminos de tiempo de ejecucin, hacer muchas lecturas y escrituras en disco, y ms si los puntos donde se
realizan estn muy separados entre ellos.
Como ejemplo, usaremos el algoritmo de ordenacin quicksort, adaptndolo para ordenar ficheros.
Usaremos el programa de ejemplo que usamos para los archivos de acceso aleatorio "alea.cpp". Y aadiremos
una nueva opcin para ordenar el archivo.
Ejemplo:
// alea2.cpp: Ejemplo de ficheros de acceso aleatorio.
// Incluye la opcin de ordenar el archivo.
#include <stdio.h>
#include <stdlib.h>
struct stRegistro {
char valido; // Campo que indica si el registro es valido S->Vlido, N>Invlido
char nombre[34];
int dato[4];
};
int Menu();
void Leer(stRegistro ®);
void Mostrar(stRegistro ®);
void Listar(long n, stRegistro ®);
long LeeNumero();
void Empaquetar(FILE *fa);
void Ordenar(FILE *fa);
void Intercambia(FILE *fa, long iz, long de);
char *LeeCampo(FILE *fa, long n, char *buf);
void QuickSort(FILE *fa, long inicio, long final);
int main()
{
stRegistro reg;
FILE *fa;
int opcion;
long numero;
fa = fopen("alea.dat", "r+b");
escribir
// si el fichero no existe, lo
do {
opcion = Menu();
switch(opcion) {
case '1': // Aadir registro
Leer(reg);
// Insertar al final:
fseek(fa, 0, SEEK_END);
fwrite(®, sizeof(stRegistro), 1, fa);
break;
case '2': // Mostrar registro
system("cls");
printf("Mostrar registro: ");
numero = LeeNumero();
fseek(fa, numero*sizeof(stRegistro), SEEK_SET);
fread(®, sizeof(stRegistro), 1, fa);
Mostrar(reg);
break;
case '3': // Eliminar registro
system("cls");
printf("Eliminar registro: ");
numero = LeeNumero();
fseek(fa, numero*sizeof(stRegistro), SEEK_SET);
fread(®, sizeof(stRegistro), 1, fa);
reg.valido = 'N';
fseek(fa, numero*sizeof(stRegistro), SEEK_SET);
fwrite(®, sizeof(stRegistro), 1, fa);
break;
case '4': // Mostrar todo
rewind(fa);
numero = 0;
system("cls");
printf("Nombre
Datos\n");
while(fread(®, sizeof(stRegistro), 1, fa)) Listar(numero++,
reg);
system("PAUSE");
break;
case '5': // Eliminar marcados
Empaquetar(fa);
break;
case '6': // Ordenar
Empaquetar(fa);
Ordenar(fa);
break;
}
} while(opcion != '0');
fclose(fa);
return 0;
}
// Muestra un men con las opciones disponibles y captura una opcin del
usuario
int Menu()
{
char resp[20];
do {
system("cls");
printf("MENU PRINCIPAL\n");
printf("--------------\n\n");
printf("1- Insertar registro\n");
printf("2- Mostrar registro\n");
printf("3- Eliminar registro\n");
printf("4- Mostrar todo\n");
printf("5- Eliminar registros marcados\n");
printf("6- Ordenar fichero\n");
printf("0- Salir\n");
fgets(resp, 20, stdin);
} while(resp[0] < '0' && resp[0] > '6');
return resp[0];
}
// Permite que el usuario introduzca un registro por pantalla
void Leer(stRegistro ®)
{
int i;
char numero[6];
system("cls");
printf("Leer registro:\n\n");
reg.valido = 'S';
printf("Nombre: ");
fgets(reg.nombre, 34, stdin);
// la funcin fgets captura el retorno de lnea, hay que eliminarlo:
for(i = strlen(reg.nombre)-1; i && reg.nombre[i] < ' '; i--)
reg.nombre[i] = 0;
for(i = 0; i < 4; i++) {
printf("Dato[%1d]: ", i);
fgets(numero, 6, stdin);
reg.dato[i] = atoi(numero);
}
}
// Muestra un registro en pantalla, si no est marcado como borrado
void Mostrar(stRegistro ®)
{
int i;
system("cls");
if(reg.valido == 'S') {
printf("Nombre: %s\n", reg.nombre);
for(i = 0; i < 4; i++) printf("Dato[%1d]: %d\n", i, reg.dato[i]);
}
system("PAUSE");
}
// Muestra un registro por pantalla en forma de listado,
// si no est marcado como borrado
void Listar(long n, stRegistro ®)
{
int i;
if(reg.valido == 'S') {
}
} while(iz <= de);
if(inicio < de) QuickSort(fa, inicio, de);
if(iz < final) QuickSort(fa, iz, final);
}
char *LeeCampo(FILE *fa, long n, char *buf)
{
stRegistro reg;
fseek(fa, n*sizeof(stRegistro), SEEK_SET);
fread(®, sizeof(stRegistro), 1, fa);
strcpy(buf, reg.nombre);
return buf;
}
void Intercambia(FILE *fa, long iz, long de)
{
stRegistro reg1, reg2;
fseek(fa, iz*sizeof(stRegistro), SEEK_SET);
fread(®1, sizeof(stRegistro), 1, fa);
fseek(fa, de*sizeof(stRegistro), SEEK_SET);
fread(®2, sizeof(stRegistro), 1, fa);
fseek(fa, iz*sizeof(stRegistro), SEEK_SET);
fwrite(®2, sizeof(stRegistro), 1, fa);
fseek(fa, de*sizeof(stRegistro), SEEK_SET);
fwrite(®1, sizeof(stRegistro), 1, fa);
}
El algoritmo que hemos usado es bastante bueno para ordenar ficheros, ya que requiere muy pocos intercambios
de registros, pero de todos modos, con ficheros grandes puede ser un proceso muy lento. En general es preferible
no ordenar los ficheros, salvo que sea muy necesario.
7 Ficheros de ndices
Mantener grandes ficheros de datos ordenados es muy costoso, ya que requiere mucho tiempo de procesador.
Afortunadamente, existe una alternativa mucho mejor: indicarlos (o indexarlos).
Para indicar un archivo normalmente se suele generar un archivo auxiliar de ndices. Existen varios mtodos, de
los que veremos algunos. El ms sencillo es crear un archivo plano que slo contenga registros con dos campos: el
campo o la expresin por la que queremos ordenar el archivo, y un campo con un ndexe que almecene la posicin
del registro indicado en el archivo de datos.
Por ejemplo, supongamos que tenemos un archivo de datos con la siguiente estructura de registro:
struct stRegistro {
char nombre[32];
char apellido[2][32];
char telefono[12];
char calle[45];
int numero;
char ciudad[32];
char fechaNacimiento[9]; // formato AAAAMMDD: Ao, mes y da
char estadoCivil;
int hijos;
}
Imaginemos que necesitamos buscar un registro a partir del nmero de telfono. Si no tenemos el archivo
ordenado por ese campo, estaremos obligados a leer todos los registros del archivo hasta encontrar el que
buscamos, y si el nmero no est, tendremos que leer todos los registros que existan.
Si tenemos el archivo ordenado por nmeros de telfono podremos aplicar un algoritmo de bsqueda. Pero si
tambin queremos hacer bsquedas por otros campos, estaremos obligados a ordenar de nuevo el archivo.
La solucin es crear un fichero de ndices, cada registro de este archivo tendr la siguiente estructura:
struct stIndiceTelefono {
char telefono[12];
long indice;
}
Crearemos el fichero de ndices a partir del archivo de datos, asignando a cada registro el campo "telefono" y el
nmero de registro correspondiente. Veamos un ejemplo:
000: [Fulanito] [Prez] [Sanchez] [12345678] [Mayor] [15] [Lisboa]
[19540425] [S] [0]
001: [Fonforito] [Fernandez] [Lpez] [84565456] [Baja] [54] [Londres]
[19750924] [C] [3]
002: [Tantolito] [Jimenez] [Fernandez] [45684565] [Alta] [153] [Berlin]
[19840628] [S] [0]
003: [Menganito] [Sanchez] [Lpez] [23254532] [Diagonal] [145] [Barcelona]
[19650505] [C] [1]
004: [Tulanito] [Sanz] [Sanchez] [54556544] [Pez] [18] [Dubln] [19750111]
[S] [0]
Y lo ordenamos:
[12345678][000]
[23254532][003]
[45684565][002]
[54556544][004]
[84565456][001]
Ahora, cuando queramos buscar un nmero de telfono, lo haremos en el fichero de ndices, por ejemplo el
"54556544" ser el registro nmero 3, y le corresponde el ndice "004". Con ese ndice podemos acceder
directamente al archivo de datos, y veremos que el nmero corresponde a "Tulanito Sanz Sanchez".
Por supuesto, nada nos impide tener ms ficheros de ndices, para otros campos.
El mayor problema es mantener los ficheros de ndices ordenados a medida que aadimos, eliminamos o
modificamos registros. Pero al ser los registros de ndices ms pequeos, los ficheros son ms manejables,
pudiendo incluso almacenarse en memoria en muchos casos.
Ejemplo
Veramos un ejemplo de implementacin de ndices:
// indices.cpp: Ejemplo de ficheros de acceso aleatorio con ndices.
#include <stdio.h>
#include <stdlib.h>
struct stRegistro {
char valido; // Campo que indica si el registro es valido S->Vlido, N>Invlido
char nombre[34];
char apellido[2][34];
char telefono[10];
};
struct stIndice {
char telefono[10];
long indice;
};
int Menu();
void Capturar(stRegistro ®);
void EliminarRetornoLinea(char *cad);
void Leer(FILE *fa, stRegistro &:reg, char *telefono);
void Insertar(FILE *fa, stRegistro ®);
void Mostrar(stRegistro ®);
char resp[20];
do {
system("cls");
printf("MENU PRINCIPAL\n");
printf("--------------\n\n");
printf("1- Insertar registro\n");
printf("2- Buscar registro\n");
printf("3- Reindicar archivo\n");
printf("4- Listar por orden de telfonos\n");
printf("5- Listar por orden natural\n");
printf("0- Salir\n");
fgets(resp, 20, stdin);
} while(resp[0] < '0' && resp[0] > '5');
return resp[0];
}
// Permite que el usuario introduzca un registro por pantalla
void Capturar(stRegistro ®)
{
int i;
char numero[6];
system("cls");
printf("Leer registro:\n\n");
reg.valido = 'S';
printf("Nombre: ");
fgets(reg.nombre, 34, stdin);
EliminarRetornoLinea(reg.nombre);
printf("Primer apellido: ");
fgets(reg.apellido[0], 34, stdin);
EliminarRetornoLinea(reg.apellido[0]);
printf("Segundo apellido: ");
fgets(reg.apellido[1], 34, stdin);
EliminarRetornoLinea(reg.apellido[1]);
printf("Telfono: ");
fgets(reg.telefono, 10, stdin);
EliminarRetornoLinea(reg.telefono);
}
// Elimina los caracteres de retorno de lnea al final de cadena
void EliminarRetornoLinea(char *cad)
{
int i;
// la funcin fgets captura el retorno de lnea, hay que eliminarlo:
for(i = strlen(cad)-1; i >= 0 && cad[i] < ' '; i--) cad[i] = 0;
}
// Muestra un registro en pantalla, si no est marcado como borrado
void Mostrar(stRegistro ®)
{
int i;
if(reg.valido == 'S') {
printf("Nombre: %s %s %s\n", reg.nombre, reg.apellido[0],
reg.apellido[1]);
printf("Nmero de telfono: %s\n", reg.telefono);
}
system("PAUSE");
}
// Lee el registro desde el fichero de datos con el telfono dado
void Leer(FILE *fa, stRegistro ®, char *telefono)
{
FILE *fi;
stIndice ind;
long inf, sup, n, nRegs;
fi = fopen("indices.ind", "rb");
fseek(fi, 0, SEEK_END);
nRegs = ftell(fi)/sizeof(stIndice);
// Bsqueda binaria:
inf = 0;
sup = nRegs-1;
do {
n = inf+(sup-inf)/2;
fseek(fi, n*sizeof(stIndice), SEEK_SET);
fread(&ind, sizeof(stIndice), 1, fi);
if(strcmp(ind.telefono, telefono) < 0) inf = n+1;
else sup = n-1;
} while(inf <= sup && strcmp(ind.telefono, telefono));
// Si se encontr el telfono, lee el registro, si no muestra mensaje.
if(!strcmp(ind.telefono, telefono)) {
fseek(fa, ind.indice*sizeof(stRegistro), SEEK_SET);
fread(®, sizeof(stRegistro), 1, fa);
}
else {
reg.valido = 'N';
printf("Registro no encontrado\n");
}
fclose(fi);
}
// Aade un registro al archivo de datos y reconstruye los ndices
void Insertar(FILE *fa, stRegistro ®)
{
// Insertar al final:
fseek(fa, 0, SEEK_END);
fwrite(®, sizeof(stRegistro), 1, fa);
ReconstruirIndices(fa);
}
// Lista todos los registros ordenados por el nmero de telfono
void ListarPorTelefonos(FILE *fa)
{
FILE *fi;
stIndice ind;
stRegistro reg;
system("cls");
fi = fopen("indices.ind", "rb");
while(fread(&ind, sizeof(stIndice), 1, fi)) {
fseek(fa, ind.indice*sizeof(stRegistro), SEEK_SET);
fread(®, sizeof(stRegistro), 1, fa);
printf("%s %s %s %s\n", reg.nombre, reg.apellido[0],
reg.apellido[1], reg.telefono);
}
fclose(fi);
system("PAUSE");
}
// Lista todos los registros del archivo de datos por el orden en que se
// insertaron.
void ListarNatural(FILE *fa)
{
stRegistro reg;
rewind(fa);
system("cls");
while(fread(®, sizeof(stRegistro), 1, fa))
printf("%s %s %s %s\n", reg.nombre, reg.apellido[0],
reg.apellido[1], reg.telefono);
system("PAUSE");
}
// Reconstruye el archivo de ndices
void ReconstruirIndices(FILE *fa)
{
long n=0;
FILE *fi;
stRegistro reg;
stIndice ind;
// Crea el fichero de ndices a partir del archivo de datos:
fi = fopen("indices.ind", "w+b");
rewind(fa);
while(fread(®, sizeof(stRegistro), 1, fa)) {
strcpy(ind.telefono, reg.telefono);
ind.indice = n++;
fwrite(&ind, sizeof(stIndice), 1, fi);
}
// Ordena usando el algoritmo Quicksort:
QuickSort(fi, 0, n-1);
fclose(fi);
}
// Implementacin del algoritmo Quicksort para fichero de ndices
void QuickSort(FILE *fi, long inicio, long final)
{
long iz, de;
char mitad[10];
static char cad[10];
iz = inicio;
de = final;
strcpy(mitad, LeeCampo(fi, (iz+de)/2, cad));
do {
while(strcmp(LeeCampo(fi, iz, cad), mitad) < 0 && iz < final) iz++;
while(strcmp(mitad, LeeCampo(fi, de, cad)) < 0 && de > inicio) de--;
if(iz < de) Intercambia(fi, iz, de);
if(iz <= de) {
iz++;
de--;
}
} while(iz <= de);
if(inicio < de) QuickSort(fi, inicio, de);
if(iz < final) QuickSort(fi, iz, final);
}
char *LeeCampo(FILE *fi, long n, char *buf)
{
stIndice ind;
fseek(fi, n*sizeof(stIndice), SEEK_SET);
fread(&ind, sizeof(stIndice), 1, fi);
strcpy(buf, ind.telefono);
return buf;
}
void Intercambia(FILE *fi, long iz, long de)
{
stIndice reg1, reg2;
fseek(fi, iz*sizeof(stIndice), SEEK_SET);
fread(®1, sizeof(stIndice), 1, fi);
fseek(fi, de*sizeof(stIndice), SEEK_SET);
fread(®2, sizeof(stIndice), 1, fi);
fseek(fi, iz*sizeof(stIndice), SEEK_SET);
fwrite(®2, sizeof(stIndice), 1, fi);
fseek(fi, de*sizeof(stIndice), SEEK_SET);
fwrite(®1, sizeof(stIndice), 1, fi);
}
An no hemos llegado al mayor nivel de optimizacin, nuestro ltimo ejemplo requiere reconstruir el fichero de
ndices cada vez que se aade o se elimina un registro.
La estructura para cada nodo del rbol es una extensin de la que usamos en el captulo anterior, tan slo
aadiremos dos campos extra para apuntar a otros nodos:
struct stNodo {
char telefono[10];
long indice;
long menor, mayor;
};
Crearemos el fichero de ndices estructurados en rbol a partir del archivo de datos, asignando a cada registro
el campo "telefono" y el nmero de registro correspondiente y aadiendo los enlaces a otros nodos. Veamos un
ejemplo:
000: [Fulanito] [Prez] [Sanchez] [12345678] [Mayor] [15] [Lisboa]
[19540425] [S] [0]
001: [Fonforito] [Fernandez] [Lpez] [84565456] [Baja] [54] [Londres]
[19750924] [C] [3]
002: [Tantolito] [Jimenez] [Fernandez] [45684565] [Alta] [153] [Berlin]
[19840628] [S] [0]
003: [Menganito] [Sanchez] [Lpez] [23254532] [Diagonal] [145] [Barcelona]
[19650505] [C] [1]
004: [Tulanito] [Sanz] [Sanchez] [54556544] [Pez] [18] [Dubln] [19750111]
[S] [0]
Veremos cmo se actualiza el fichero de ndices a medida que insertamos registros en el archivo de datos:
Paso uno:
[12345678][000][---][---]
Paso dos:
[12345678][000][---][001] <-[84565456][001][---][---]
Paso tres:
[12345678][000][---][001]
[84565456][001][002][---] <-[45684565][002][---][---]
Paso cuatro:
[12345678][000][---][001]
[84565456][001][002][---]
[45684565][002][003][---] <-[23254532][003][---][---]
Paso cinco:
[12345678][000][---][001]
[84565456][001][002][---]
[45684565][002][003][004] <-[23254532][003][---][---]
[54556544][004][---][---]
Como puede observarse, cada vez que se inserta un registro de datos, tan slo hay que insertar un registro de
ndice y modificar otro.
Eliminar registros
Supongamos que queremos eliminar un registro de datos. En el archivo de datos simplemente lo marcamos
como borrado. En teora, mientras el registro no se elimine fsicamente, no ser necesario eliminar el registro
de ndice asociado. Simplemente estar apuntando a un registro marcado como borrado. Posteriormente,
cuando purguemos el archivo de datos ser necesario reconstruir el fichero de ndices.
Duplicacin de claves
No hay inconveniente en almacenar registros con claves duplicadas, tan slo habr que tener en cuenta que
tendremos que almacenar un nodo para cada uno de ellos. Tomaremos un criterio para el rbol, la rama 'menor',
y pasar a ser la rama 'menor o igual'.
Ventajas y desventajas
Este mtodo tiene la ventaja de que no es necesario ordenar el archivo de ndices, pero puede producir
resultados mediocres o francamente malos. Por ejemplo, si los registros se introducen ordenados, buscar por la
clave del ltimo registro insertado requerir leer todos los nodos del rbol.