Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Archivos Secuencial y Directos
Archivos Secuencial y Directos
Ficheros
Metodologa de la Programacin II
Tema 2
2.1 Definicin de fichero
Un fichero en sentido global puede ser desde un monitor hasta una impresora, pasando por un
archivo en disco.
La idea ms comn del concepto de fichero es un conjunto de posiciones de memoria situadas en
un disco de los dispositivos externos de almacenamiento del sistema, en las cuales podemos
almacenar y recuperar informacin.
El lenguaje C nos proporciona un acceso secuencial y directo a los registros de un fichero, pero
no soporta el acceso indexado a un registro dado.
Los ficheros en C los podemos clasificar, segn la informacin que contengan, en dos grupos:
Tipo FILE:
C define la estructura de datos FILE en el fichero de cabecera "stdio.h" para el manejo de
ficheros. Nosotros siempre usaremos punteros a estas estructuras.
La definicin de sta estructura depende del compilador, pero en general mantienen un campo
con la posicin actual de lectura / escritura, un buffer para mejorar las prestaciones de acceso
al fichero y algunos campos para uso interno.
2.2 Ficheros de texto y ficheros binarios.
Los ficheros de texto se caracterizan por estar compuestos por una serie de caracteres
organizados en lneas terminadas por un carcter de nueva lnea (carcter '\n'). Esto nos hace
pensar en la idea de usar la impresora como si fuese un fichero de texto.
Por otro lado, los ficheros binarios constan de una secuencia de bytes. Podemos decir que
cualquier fichero que no sea de texto, ser binario.
A la hora de trabajar con ficheros, tendremos que especificar antes de usarlos, si sern de texto o
binarios.
Podemos establecer una segunda clasificacin de los ficheros, atendiendo al modo de acceso a su
informacin. De este modo, distinguiremos entre ficheros secuenciales y ficheros directos.
Los ficheros de acceso secuencial se basan en el hecho de que, para acceder a una determinada
posicin de los mismos, hemos de recorrer desde el principio todas las posiciones hasta llegar a la
deseada. Las impresoras son un claro ejemplo de acceso secuencial, as como las cintas
magnticas.
Con el uso de ficheros de acceso directo podemos acceder de forma directa a la posicin que
queramos sin tener que pasar por las posiciones anteriores. El dispositivo de acceso directo por
excelencia es el dico magntico.
90
91
Tema 2
Modo acceso es el modo de apertura del fichero, mediante el cual le diremos al compilador si se
trata de un fichero de texto o binario, y si vamos a leer o escribir en el fichero.
Los distintos modos de abrir un fichero son los siguientes:
Modo Significado
r
r+
w+
a+
Abre el archivo de texto para aadir al final, con opcin de lectura. Si el fichero
no existe, lo crea.
rb
wb
ab
rb +
Abre el archivo binario para aadir al final, con opcin de lectura. Si el fichero
no existe, lo crea.
Decir que los ficheros de texto se pueden referenciar tambien como "rt", "wt", "at", "rt+", "wt+" y
"at+", pero para facilitar ms las cosas, cuando no especificamos si queremos abrir un fichero de
texto o binario, por defecto se supone que es de texto, es por ello que los modos "r", "w" y "a" se
refieren a ficheros de texto.
Hemos de tener cuidado con los modos de apertura de los ficheros por que al abrir con cualquier
modo "w", si ya exista el fichero, se borrar la informacin que contuviese. Si no existe, lo
crear. Por el contrario con el modo "a" tambin lo crear si no existe el fichero, pero si ya existe,
aadir al final del mismo los datos que escribamos.
Podemos saber si un fichero ha sido abierto correctamente o no. Debido a que los ficheros son
punteros a estructuras tipo FILE, podemos conocer su valor al hacer que apunten a ellas. La
forma de hacerlo es comparando el puntero con la constante predefinida NULL. De esta forma,
92
Metodologa de la Programacin II
93
Tema 2
{
printf( "Error: fichero NO CERRADO\n" );
return 1;
}
return 0;
}
fclose
<valor> = fclose (<variable_fichero>);
Donde:
valor es el valor que nos dir si ha ocurrido algn error cerrando el fichero. 0 indica que todo ha
ido bien.
variable_fichero es la variable declarada de la forma: FILE *<variable_fichero>
Cuando terminemos de trabajar con un fichero hemos de realizar la operacin de cierre del
fichero. Si no lo hacemos podemos ocasionar la prdida de todos los datos del mismo. Si se
necesitan cerrar varios ficheros a la vez, nos podemos ahorrar varios fclose utilizando la funcion
fcloseall.
#include <stdio.h>
int main()
{
FILE *fichero;
char nombre[10] = "datos.dat";
fichero = fopen( nombre, "w" );
printf( "Fichero: %s -> ", nombre );
if( fichero )
printf( "creado (ABIERTO)\n" );
else
{
printf( "Error (NO ABIERTO)\n" );
return 1;
}
if( !fclose(fichero) )
printf( "Fichero cerrado\n" );
else
{
printf( "Error: fichero NO CERRADO\n" );
return 1;
}
return 0;
}
fcloseall
<numero_ficheros> = fcloseall();
94
95
Tema 2
Ni que decir tiene que cuando usemos funciones para escribir en ficheros, estos tendrn que haber
sido abiertos en modo escritura o para aadir datos.
#include <stdio.h>
int main()
{
char nombre[10]="datos.dat";
FILE *fichero;
int i;
fichero = fopen( nombre, "a" );
printf( "Fichero: %s -> ", nombre );
if( fichero )
printf( "existe o ha sido creado (ABIERTO)\n" );
else
{
printf( "Error (NO ABIERTO)\n" );
return 1;
}
printf( "Escribimos las 18 primeras letras del abecedario ingles en el
fichero: %s\n\n", nombre );
for( i=0; i<18; i++)
printf( "%c", fputc('a'+i, fichero) );
if( !fclose(fichero) )
printf( "\nFichero cerrado\n" );
else
{
printf( "\nError: fichero NO CERRADO\n" );
return 1;
}
return 0;
}
fgetc
Esta funcin lee un carcter de un fichero de texto abierto en modo lectura. Devuelve un entero si
la lectura es correcta. De otra forma devuelve el valor EOF que indica que hemos llegado al final
del fichero. Su formato es:
<carcter> = fgetc (<var_fich>);
Donde:
carcter es la variable que contendr el carcter ledo del fichero.
var_fich es la variable declarada como FILE.
Ejemplos:
c = fgetc (f1);
96
97
Tema 2
fputs ("hola\n", f1);
strcpy (cad, "hola\n"); fputs (cad, f1);
fgets
Esta funcin lee un nmero de caracteres de un fichero almacenndolos en una cadena. Si se
encuentra el carcter de nueva lnea ya no almacenar ms caracteres. Su formato es:
fgets (<cadena>, <num_caracteres>, <var_fich>);
Donde:
cadena es la variable que almacenar la cadena leda del fichero.
num_caracteres es el nmero de caracteres que se desea leer.
var_fich es la variable declarada como FILE.
Ejemplos:
fgets (cad, 5, f1);
fwrite
Esta funcin permite escribir uno o ms datos o bloques de datos binarios en un fichero. Su
formato es el siguiente:
fwrite (<dato>, <num_bytes>, <cont>, <var_fich>);
Donde:
dato es el dato o datos que se van a escribir en el fichero. Hemos de especificar su direccin de
memoria mediante el operador "&", ya que realmente es un puntero que apunta a la zona de
memoria intermedia donde se almacenar temporalmente la informacin antes de pasarla al
fichero.
num_bytes es el nmero de bytes que se van a escribir. Lo ms corriente es usar para este
argumento el operador sizeof sobre el dato a escribir.
contes el nmero de datos, de num_bytes cada uno que se van a escribir.
var_fich es el puntero al fichero binario usado.
#include <stdio.h>
struct mystruct
{
int i;
98
99
Tema 2
{
printf ("Error de creacin del fichero\n");
exit (1);
}
fwrite (&f, sizeof(f), 1, fich);
fclose (fich);
/* Lectura del float del fichero */
if ((fich = fopen ("floats.dat", "rb")) == NULL)
{
printf ("Error de existencia del fichero\n");
exit (1);
}
fread (&f, sizeof(f), 1, fich);
fclose (fich);
printf ("Float = %.3f", f);
}
Vemos que hemos usado el mismo puntero de fichero. Lo podemos hacer siempre que cerremos
el primero antes de abrir el segundo fichero, o se trate del mismo fichero, como es en este caso.
Primero hemos abierto el fichero binario para escritura, con lo cual lo creamos de nuevo, y
despus lo abrimos para lectura para leer el float.
Este es un ejemplo muy sencillo pues en el fichero solo escribimos un float y despus lo leemos.
Pero, qu ocurre cuando escribimos ms de un dato? cmo sabemos hasta donde debemos leer
en el fichero?. La respuesta est en conocer donde est el final del fichero. De esta forma
leeremos hasta encontrar el final del fichero dado. Existe una funcin que nos dir si hemos
llegado a este punto o no: la funcin feof.
#include <stdio.h>
int main()
{
FILE *fichero;
char nombre[11] = "datos5.dat";
unsigned int dinero[10] = { 23, 12, 45, 345, 512, 345, 654, 287, 567, 124
};
unsigned int leer[10], i;
fichero = fopen( nombre, "w+" );
printf( "Fichero: %s -> ", nombre );
if( fichero )
printf( "creado (ABIERTO)\n" );
else
{
printf( "Error (NO ABIERTO)\n" );
return 1;
}
printf( "Escribiendo cantidades:\n\n" );
for( i=0; i<10; i++ )
printf( "%d\t", dinero[i] );
100
feof
Esta funcin nos devuelve un nmero positivo si hemos llegado al final de un fichero binario. De
otro modo, nos devuelve un cero. Su sintaxis es:
<valor> = feof (<var_fich>);
Donde:
valor es el valor que nos indica si ha llegado a final de fichero.
var_fich es la variable declarada como FILE.
Ejemplos:
while (!feof(f1))
{
fread (....);
...
}
Este bucle significa "mientras no sea final de fichero leer..."
#include <stdio.h>
int main(void)
{
FILE *stream;
/* open a file for reading */
stream = fopen("DUMMY.FIL", "r");
/* read a character from the file */
Metodologa de la Programacin II
101
Tema 2
fgetc(stream);
/* check for EOF */
if (feof(stream))
printf("We have reached end-of-file\n");
/* close the file */
fclose(stream);
return 0;
}
ferror
Determina si una operacin con archivos ha sido errnea o no. Si lo ha sido devuelve un nmero
positivo, y si no ha habido problema, devuelve un 0. Su sintaxis es:
<valor> = ferror (<var_fich>);
Donde:
valor es el valor que nos indica si ha ocurrido un error.
var_fich es la variable declarada como FILE.
Ejemplos:
fwrite (&dato, sizeof(dato), 1, fich);
if (ferror(fich)) printf ("Error de escritura\n");
#include <stdio.h>
int main(void)
{
FILE *stream;
/* open a file for writing */
stream = fopen("DUMMY.FIL", "w");
/* force an error condition by attempting to read */
(void) getc(stream);
if (ferror(stream)) /* test for an error on the stream */
{
/* display an error message */
printf("Error reading from DUMMY.FIL\n");
/* reset the error and EOF indicators */
clearerr(stream);
}
102
103
Tema 2
Ejemplos:
i = getw (fich);
printf ("%d\n", getw (fich));
#include <stdio.h>
#include <stdlib.h>
#define FNAME "test.$$$"
int main(void)
{
FILE *fp;
int word;
/* place the word in a file */
fp = fopen(FNAME, "wb");
if (fp == NULL)
{
printf("Error opening file %s\n", FNAME);
exit(1);
}
word = 94;
putw(word,fp);
if (ferror(fp))
printf("Error writing to file\n");
else
printf("Successful write\n");
fclose(fp);
/* reopen the file */
fp = fopen(FNAME, "rb");
if (fp == NULL)
{
printf("Error opening file %s\n", FNAME);
exit(1);
}
/* extract the word */
word = getw(fp);
if (ferror(fp))
printf("Error reading file\n");
else
printf("Successful read: word = %d\n", word);
104
105
Tema 2
/* reopen the file */
fp = fopen(FNAME, "rb");
if (fp == NULL)
{
printf("Error opening file %s\n", FNAME);
exit(1);
}
/* extract the word */
word = getw(fp);
if (ferror(fp))
printf("Error reading file\n");
else
printf("Successful read: word = %d\n", word);
/* clean up */
fclose(fp);
unlink(FNAME);
return 0;
}
fscanf
Esta funcin trabaja de la misma manera que scanf, pero leyendo los datos formateados de un
fichero. Su sintaxis es:
fscanf (<var_fich>, <cadena_de_control>, <lista_variables>);
Donde:
var_fich es la variable declarada como FILE.
cadena_de_control son las cadenas de control que se desean leer, tales como %d, %s, %f, %x,
etc...
lista_variables son las variables que contendrn los valores de la lectura del fichero que deben
coincidir con sus respectivas cadenas de control.
Ejemplos:
fscanf (fich, "%s%d%f", nombre, &edad, &altura);
#include <stdlib.h>
#include <stdio.h>
int main(void)
106
Metodologa de la Programacin II
107
Tema 2
{
printf( "Error (NO ABIERTO)\n" );
return 1;
}
fprintf( fichero, "Esto es un ejemplo de usar la funcion \'fprintf\'\n" );
fprintf( fichero, "\t 2\t 3\t 4\n" );
fprintf( fichero, "x\tx\tx\tx\n\n" );
for( i=1; i<=10; i++ )
fprintf( fichero, "%d\t%d\t%d\t%d\n", i, i*i, i*i*i, i*i*i*i );
fprintf( stdout, "Datos guardados en el fichero: %s\n", nombre );
if( !fclose(fichero) )
printf( "Fichero cerrado\n" );
else
{
printf( "Error: fichero NO CERRADO\n" );
return 1;
}
return 0;
}
remove
Esta funcin borra fisicamente el archivo que se especifique. Devuelve un 0 si todo ha salido
correctamente o un -1 si ha ocurrido un error. Su sintaxis es:
<valor> = remove (<nombre_archivo>);
Donde:
valor es el valor que nos dir si ha ocurrido un error al borrar el fichero.
nombre_archivo es el nombre del fichero que se desea borrar, o sea, que se refiere a una cadena
de caracteres y no al identificador del fichero.
Ejemplos:
remove ("datos.dat");
strcpy (nombre, "texto.txt"); remove (nombre);
#include <stdio.h>
int main(void)
{
char file[80];
/* prompt for file name to delete */
printf("File to delete: ");
gets(file);
/* delete the file */
108
Ejemplos:
strcpy (viejo, "clientes.xxx\0");
strcpy (nuevo, "clientes.dat\0);
rename (viejo, nuevo);
Ejemplo:
// copia.cpp: Copia de ficheros
// Uso: copia <fichero_origen> <fichero_destino>
#include <iostream.h>
#include <stdio.h>
Metodologa de la Programacin II
109
Tema 2
int main(int argc, char **argv)
{
FILE *fe, *fs;
unsigned char buffer[2048]; // Buffer de 2 Kbytes
int bytesLeidos;
if(argc != 3) {
cout << "Usar: copia <fichero_origen> <fichero_destino>" << endl;
return 1;
}
fe = fopen(argv[1], "rb"); // Abrir el fichero de entrada en lectura y binario
if(!fe) {
cout << "El fichero " << argv[1] << " no existe o no puede ser abierto." << endl;
return 1;
}
fs = fopen(argv[2], "wb"); // Crear o sobreescribir el fichero de salida en binario
if(!fs) {
cout << "El fichero " << argv[2] << " no puede ser creado." << endl;
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;
}
2.4 Gestin de un fichero secuencial de estructuras
Los componentes o registros de un fichero pueden ser de cualquier tipo de datos, desde tipos
bsicos (enteros, float, char, etc...) hasta estructuras de datos.
Las operaciones en cualquiera de los casos son siempre iguales. Veamos un ejemplo de un
fichero de estructuras:
Apertura:
...
struct Tcli
{
char nombre[30];
char direccin[30];
...
};
110
Metodologa de la Programacin II
111
Tema 2
De tal forma que todo lo que escribamos en fimp saldr por impresora. Por tal motivo, debemos
pensar que se trata de un fichero de texto, por lo cual, usaremos funciones de escritura en ficheros
de texto.
2.5 Acceso directo a ficheros
Ya hemos visto como acceder secuencialmente a un fichero, sin embargo tambin se puede hacer
de forma directa.
Supongamos que tenemos definido un fichero con la siguiente estructura de registro:
struct
{
int codigo;
char nomart[31];
float precio;
}articulo;
Es evidente que la longitud de cada registro es de 37 bytes (2+31+4 bytes). De esta forma, la
disposicin de los registros dentro del fichero en disco se realiza en las siguientes posiciones:
El acceso directo consiste en indicar la posicin a la que queremos acceder en bytes. Por ejemplo,
para acceder directamente al registro 2, indicaremos que queremos ir al byte 37, contando desde
el principio del registro. La orden que posibilita este acceso es la siguiente.
fseek
112
Valor Desde
SEEK_SET
Principio de fichero
SEEK_CUR
SEEK_END
Hay que tener muy en cuenta que no es lo mismo desplazarse 37 bytes desde el principio del
fichero (accederemos al segundo registro), que desde el lugar donde nos encontremos
(accederemos al siguiente registro)
Existe otra funcin que nos devuelve la posicin en la que nos encontramos dentro del fichero
#include <stdio.h>
long filesize(FILE *stream);
int main(void)
{
FILE *stream;
stream = fopen("MYFILE.TXT", "w+");
fprintf(stream, "This is a test");
printf("Filesize of MYFILE.TXT is %ld bytes\n", filesize(stream));
fclose(stream);
return 0;
}
long filesize(FILE *stream)
{
long curpos, length;
curpos = ftell(stream);
fseek(stream, 0L, SEEK_END);
length = ftell(stream);
fseek(stream, curpos, SEEK_SET);
Metodologa de la Programacin II
113
Tema 2
return length;
}
ftell
<posicin> = ftell (<var_fich>);
Donde:
posicin es la variable que contendr la posicin en bytes en la que nos encontramos en ese
momento en el fichero.
var_fich es la variable declarada como FILE.
Esta funcin se suele usar, a parte de para saber la situacin exacta en el fichero, para obtener la
longitud del mismo. Veamos un ejemplo:
...
if ((f=fopen("articul.dat", "rb")) == NULL)
{
printf ("Imposible crear fichero\n");
exit (1);
}
fseek (f, 0, 2); /* tambien puede ser fseek (f, 2, SEEK_END); */
l = ftell(f);
printf ("El fichero tiene un tamao de %ld bytes\n", l);
printf ("Y un total de %ld registros\n", l/sizeof(reg));
/* donde reg sera la estructura o el dato simple contenido en el fichero */
...
La forma de dar de alta registros es igual que para el acceso secuencial. Se har un recorrido
hasta llegar al final del fichero para comprobar que no existe el cdigo del registro a dar de alta, y
en ese caso, se procede a hacer fwrite. Abriremos para ello el fichero en modo aadir.
#include <stdio.h>
#include <string.h>
int main()
{
char nombre[11] = "datos4.dat", mensaje[81]="";
FILE *fichero;
long int comienzo, final;
fichero = fopen( nombre, "r" );
printf( "Fichero: %s -> ", nombre );
if( fichero )
printf( "existe (ABIERTO)\n" );
else
{
printf( "Error (NO ABIERTO)\n" );
return 1;
}
114
Las bajas consisten en localizar el registro segn un cdigo, y escribir un registro vaco en tal
posicin. Para ello tendremos que abrir el fichero en modo lectura/escritura, o sea, "r+".
Las modificaciones, de forma similar a las bajas pero escribiendo en el lugar correspondiente el
registro modificado que reemplace al actual.
Las consultas no son otra cosa que un acceso directo al cdigo indicado.
Y por ltimo los listados, que consisten en un recorrido secuencial desde el primer registro hasta
el ltimo.
Recordaros que la forma de gestin de los ficheros aqu expuesta es la ms bsica que yo
conozco, pero que se puede hacer de mil maneras, mejores o peores, pero que no es la nica.
Comentamos casi al principio que C no soporta los ficheros indexados, pero puedes crearlos, slo
debes pensar en crearos un fichero de ndices y otro maestro y a partir de ah, todo sale solo.
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
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 del
Metodologa de la Programacin II
115
Tema 2
principio y sin haber ledo toda la informacin desde el principio 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 apogeo. 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:
1. Dependiendo de la direccin del flujo de datos:
De entrada: los datos se leen por el programa desde el archivo.
De salida: los datos se escriben por el programa hacia el archivo.
De entrada/salida: los datos pueden se escritos o ledos.
2. Dependiendo del tipo de valores permitidos a cada byte:
De texto: slo estn permitidos ciertos rangos de valores para cada byte. Algunos
bytes tienen un significado especial, por ejemplo, el valor hexadecimal 0x1A marca el
fin de fichero. Si abrimos un archivo en modo texto, no ser posible leer ms all de
ese byte, aunque el fichero sea ms largo.
Binarios: estn permitidos todos lo valores para cada byte. En estos archivos el final
del fichero se detecta de otro modo, dependiendo del soporte y del sistema operativo.
La mayora de las veces se hace guardando la longitud del fichero. Cuando queramos
almacenar valores enteros, o en coma flotante, o imgenes, etc, deberemos usar este
tipo de archivos.
3. Segn el tipo de acceso:
Archivos secuenciales: imitan el modo de acceso de los antiguos ficheros secuenciales
almacenados en cintas magnticas y
Archivos de acceso aleatorio: permiten acceder a cualquier punto de ellos para realizar
lecturas y/o escrituras.
4. Segn la longitud de registro:
Longitud variable: en realidad, en este tipo de archivos no tiene sentido hablar de
longitud de registro, podemos considerar cada byte como un registro. Tambin puede
suceder que nuestra aplicacin conozca el tipo y longitud de cada dato almacenado en
116
el archivo, y lea o escriba los bytes necesarios en cada ocasin. Otro caso es cuando se
usa una marca para el final de registro, por ejemplo, en ficheros de texto se usa el
carcter de retorno de lnea para eso. En estos casos cada registro es de longitud
diferente.
Longitud constante: en estos archivos los datos se almacenan en forma de registro de
tamao contante. En C usaremos estructuras para definir los registros. C dispone de
funciones de librera adecuadas para manejar este tipo de ficheros.
Mixtos: en ocasiones pueden crearse archivos que combinen los dos tipos de registros,
por ejemplo, dBASE usa registros de longitud constante, pero aade un registro
especial de cabecera al principio para definir, entre otras cosas, el tama y el tipo de
los registros.
Es posible crear archivos combinando cada una de estas categoras, por ejemplo: archivos
secuenciales de texto de longitud de registro variable, que son los tpicos archivos de texto.
Archivos de acceso aleatorio binarios de longitud de registro constante, normalmente usados en
bases de datos. Y tambin cualquier combinacin menos corriente, como archivos secuenciales
binarios de longitud de registro constante, etc.
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:
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:
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.
En cuanto a los valores permitidos para los bytes, se puede aadir otro carcter a la cadena de
modo:
t: modo texto. Normalmente es el modo por defecto. Se suele omitir.
b: modo binario.
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.cpp: Ejemplo de ficheros de acceso aleatorio.
#include <stdio.h>
Metodologa de la Programacin II
117
Tema 2
#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");
// Este modo permite leer y escribir
if(!fa) fa = fopen("alea.dat", "w+b"); // si el fichero no existe, lo 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);
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
118
119
Tema 2
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);
while(fread(®, sizeof(stRegistro), 1, fa))
if(reg.valido == 'S') fwrite(®, sizeof(stRegistro), 1, ftemp);
fclose(ftemp);
fclose(fa);
remove("alea.bak");
rename("alea.dat", "alea.bak");
rename("alea.tmp", "alea.dat");
fa = fopen("alea.dat", "r+b");
}
120
121
Tema 2
fgets(linea, 128, fich);
}
}
// Algoritmo de mezcla:
void Mezcla(FILE *fich)
{
bool ordenado;
FILE *aux[2];
// Bucle que se repite hasta que el fichero est ordenado:
do {
// Crea los dos ficheros auxiliares para separar los tramos:
aux[0] = fopen("aux1.txt", "w+");
aux[1] = fopen("aux2.txt", "w+");
rewind(fich);
Separar(fich, aux);
rewind(aux[0]);
rewind(aux[1]);
rewind(fich);
ordenado = Mezclar(fich, aux);
fclose(aux[0]);
fclose(aux[1]);
} while(!ordenado);
// Elimina los ficheros auxiliares:
remove(aux[0]);
remove(aux[1]);
}
// Separa los tramos ordenados alternando entre los ficheros auxiliares:
void Separar(FILE *fich, FILE **aux)
{
char linea[128], anterior[2][128];
int salida = 0;
// Volores iniciales para los ltimos valores
// almacenados en los ficheros auxiliares
strcpy(anterior[0], "");
strcpy(anterior[1], "");
// Captura la primero lnea:
fgets(linea, 128, fich);
while(!feof(fich)) {
// Decide a qu fichero de salida corresponde la lnea leda:
if(salida == 0 && strcmp(linea, anterior[0]) < 0) salida = 1;
else if(salida == 1 && strcmp(linea, anterior[1]) < 0) salida = 0;
// Almacena la lnea actual como la ltima aadida:
strcpy(anterior[salida], linea);
// Aade la lnea al fichero auxiliar:
fputs(linea, aux[salida]);
122
123
Tema 2
while(!feof(aux[1])) {
fputs(linea[1], fich);
fgets(linea[1], 128, aux[1]);
}
return(tramos == 1);
}
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.
2.7 Ordenar ficheros de acceso aleatorio
Cuando trabajemos con ficheros de acceso secuencial con tamao de registro constante,
podremos aplicar los mismos algoritmos de ordenacin que con tablas en memoria, ya que es
posible acceder a cada registro para lectura y escritura.
En el caso de ficheros de acceso aleatorio con tamao de registro variable, los trataremos como si
fueran secuenciales.
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 ®);
124
125
Tema 2
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);
126
127
Tema 2
ftemp = fopen("alea.tmp", "wb");
rewind(fa);
while(fread(®, sizeof(stRegistro), 1, fa))
if(reg.valido == 'S') fwrite(®, sizeof(stRegistro), 1, ftemp);
fclose(ftemp);
fclose(fa);
remove("alea.bak");
rename("alea.dat", "alea.bak");
rename("alea.tmp", "alea.dat");
fa = fopen("alea.dat", "r+b");
}
void Ordenar(FILE *fa)
{
long nRegs;
fseek(fa, 0, SEEK_END);
nRegs = ftell(fa)/sizeof(stRegistro);
QuickSort(fa, 0L, nRegs-1);
}
void QuickSort(FILE *fa, long inicio, long final)
{
long iz, de;
char mitad[34];
static char cad[34];
iz = inicio;
de = final;
strcpy(mitad, LeeCampo(fa, (iz+de)/2, cad));
do {
while(strcmp(LeeCampo(fa, iz, cad), mitad) < 0 && iz < final) iz++;
while(strcmp(mitad, LeeCampo(fa, de, cad)) < 0 && de > inicio) de--;
if(iz < de) Intercambia(fa, iz, de);
if(iz <= de) {
iz++;
de--;
}
} 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);
128
Metodologa de la Programacin II
129
Tema 2
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]
Generamos un fichero de ndices:
[12345678][000]
[84565456][001]
[45684565][002]
[23254532][003]
[54556544][004]
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.
130
131
Tema 2
opcion = Menu();
switch(opcion) {
case '1': // Insertar registro
Capturar(reg);
Insertar(fa, reg);
break;
case '2': // Buscar registro
system("cls");
printf("Buscar registro: ");
do {
fgets(telefono, 10, stdin);
EliminarRetornoLinea(telefono);
} while(strlen(telefono) < 1);
Leer(fa, reg, telefono);
Mostrar(reg);
break;
case '3': // Indicar archivo
system("cls");
printf("Indicando archivo: ");
ReconstruirIndices(fa);
break;
case '4': // Mostrar todo por orden de telfonos
ListarPorTelefonos(fa);
break;
case '5': // Mostrar todo por orden natural
ListarNatural(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- 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);
132
133
Tema 2
{
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)) {
134
135
Tema 2
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.
136