Ejemplos java y C/linux

Tutoriales

Enlaces

Licencia

Creative Commons License
Esta obra está bajo una licencia de Creative Commons.
Para reconocer la autoría debes poner el enlace https://old.chuidiang.org

Acceso aleatorio a un fichero

En el ejemplo de copia de un fichero binario vimos como abrir un fichero para lectura y escritura. También vimos como leer de él o escribir.

En ese ejemplo, tanto la lectura como la escritura eran secuenciales. Es decir, se abría el fichero desde el principio del fichero y se iban leyendo o escribiendo todos los datos del fichero en orden, desde el primero hasta el último, secuencialmente.

En la mayoría de los casos esto nos puede servir. Leemos el contenido completo del fichero y trabajamos con él. O abrimos el fichero para escribir los resultados y los vamos escribiendo todos seguidos.

Sin embargo, hay casos en que esto se nos puede quedar escaso. Hay veces en que necesitamos acceder una y otra vez al fichero para leer sus datos, modificar parte de ellos, por el medio. Imagina, por ejemplo, que tenemos guardado en un fichero nuestra agenda de teléfonos, con nuestros amigos. A veces podemos querer consultar uno que esté en medio del fichero, o cambiar su número de teléfono, o borrarlo como amigo porque nos hemos peleado.

C nos ofrece esta posibilidad. La función fseek() nos permite desplazarnos rápidamente, sin necesidad de ir leyendo todo, hasta una posición concreta de un fichero. Esto es lo que se conoce como acceso aleatorio a un fichero.

Definimos una estructura y rellenamos el fichero

Siguiendo con el ejemplo de la agenda, supón que tenemos una estructura de datos en C para guardar nuestros amigos. Como somos vaguetes, sólo pondremos dos campos, el nombre y el teléfono.

typedef struct {
   char nombre[20];
   int edad;
} persona;

Con esta estructura vamos a hacer un pequeño relleno de un fichero, para tener algo con lo que jugar. El código puede ser este

FILE *f1;
persona dato;
int i;

/* Abrimos el fichero binario y de escritura */
f1 = fopen ("persona.dat", "wb");
if (f1 == NULL)
{
   perror("No se puede abrir persona.dat");
   return -1;
}

/* Escribimos 10 datos, que serán
* Juan0, Juan1, Juan2, Juan3...
* con edad 0,1,2,3....
*/

for (i=0; i<10; i++)
{
   sprintf (dato.nombre, "Juan%d", i);
   dato.edad=i;
   /* El tamaño es sizeof(dato) y escribimos un registro */
   fwrite (&dato, sizeof(dato), 1, f1);
}

/* Cerramos el fichero */
fclose(f1);

A diferencia del código de ejemplo de copia de ficheros, esta vez el tamaño del dato es sizeof(dato) mientras que en el ejemplo anterior era 1. En este caso escribimos un solo dato cada vez, mientras que antes escribiamos de 1000 en 1000.

Ir a un registro concreto para leer

Imaginemos ahora que una vez que tenemos el fichero, queremos leer la persona que ocupa la cuarta posición. Su índice es 3, ya que la numeración comienza por cero.

La función fseek() nos permite ir a una posición concreta. La función fseek() admite tres parámetros que son:

Vamos a ver unos ejemplillos para que quede más claro.

Si ponemos fseek(f1, 100, SEEK_SET) nos situaremos en el byte 100 del fichero, empezando a contar desde el principio.

Si ponemos fseek(f1, 100, SEEK_END) nos situaremos 100 bytes antes del último byte del fichero.

Si acabamos de abrir el fichero y leemos cien bytes y luego hacemos fseek(f1, 100, SEEK_CUR), nos situaremos 100 bytes después del último byte leido, que era el 100, así que nos situaremos en el 200.

Volvemos al ejemplo de la agenda. Queremos el registro número 3 (que en realidad es el cuarto en el fichero). Lo primero que hay que hacer es calcular en que byte empieza ese registro. Esto es sencillo. Cada registro tiene sizeof(dato) bytes. El primero empezará en el 0, el segundo en sizeof(dato), el tercero en 2*sizeof(dato) y así sucesivamente.

Nuestro registro empieza en N*sizeof(dato) siendo N el número de registro, empezando a contar en cero. Para nuestro ejemplo, la posición es 3*sizeof(dato).

Sabiendo esto, está chupado irse a esa posición y leerla.

/* Abrimos el fichero para lectura y escritura. Más adelante querremos modificar el dato. */
f1 = fopen ("persona.dat", "rb+");
if (f1 == NULL)
{
   perror("No se puede abrir persona.dat");
   return -1;
}

/* Vamos al cuarto registro, indice 3 */
fseek (f1, 3*sizeof(dato), SEEK_SET);

/* Leemos y sacamos el resultado por pantalla */
fread (&dato, sizeof(dato), 1, f1);
printf ("nombre = %s\n", dato.nombre);
printf ("edad = %d\n", dato.edad);

Ya estamos en la posición justa, hemos leído y sacado por pantalla nuestro cuarto contacto.

Modificar los datos de un registro

Vamos ahora a modificar este mismo registro, porque lo de Juan3 no nos hace gracia y queremos que se llame Pedro. Además, su edad es 33 y no 3.

Puesto que acabamos de leer, estamos justo detrás del registro número 3. Así que debemos situarnos nuevamente al principio de este registro. Luego, simplemente rellenamos los datos a nuestro gusto y escribimos

/* Modificamos los datos en la estructura */
sprintf (dato.nombre, "Pedro");
dato.edad=33;

/* Nos volvemos a situar al principio del registro 3 */
fseek(f1, 3*sizeof(dato), SEEK_SET);

/* ¡¡ y lo machacamos !! */
fwrite (&dato, sizeof(dato), 1, f1);

Ya está, se acaba de machacar nuestro amigo Juan3 y ahora se llama Pedro.

Saber cuántos amigos tenemos

Si queremos saber cuántos registros hay el fichero, la función ftell() nos puede ayudar. ftell() nos indica en qué posición en bytes del fichero estamos.

Si con fseek() nos vamos al final del fichero y luego con ftell() preguntamos dónde estamos, obtendremos el número de bytes del fichero, ya que estamos en el último. Dividiendo este número total de bytes entre lo que ocupa cada registro, sabremos cuántos registros tenemos.

El código puede ser así.

fseek(f1, 0, SEEK_END);
int numeroRegistros = ftell(f1)/sizeof(dato);

Borrar un registro

No insistas, no se puede. No podemos borrar un registro del fichero, ya que ocupa un espacio en bytes que seguirá ocupando.

¿Cómo borro entonces a alguien de mi agenda?

Tienes dos soluciones, la chapucera y la que tarda mucho.

La solución chapucera consiste en decidir que un determinado nombre o valor de edad indica que ese amigo no existe. Por ejemplo, un amigo sin nombre o de edad cero puede decir que ese registro se ha borrado. Cuando leamos la lista de amigos del fichero, antes de sacarlos por pantalla debemos comprobar si tiene o no edad cero y si la tiene, no lo sacamos por pantalla y vamos al siguiente. Si no hay ningún valor susceptible de convertirse en indicador de amigo no existente (por ejemplo, tenemos amigos sin nombre y de edad cero, así que no podemos usar eso), no nos quedará más remedio que añadir un nuevo campo en nuestra estructura que indique si el amigo vale o no. Cuando queramos borrar un amigo, simplemente le ponemos un cero en la edad.

La solución que tarda mucho consiste en cuando queremos borrar un amigo, desplazamos todos los que van detrás una posición antes. Es decir, si queremos borrar al amigo 3, debemo coger al amigo 4 y ponerlo encima del 3, machacando. Luego el 5 encima del 4 y así sucesivamente. ¿Qué pasa con el último, el de la posición N?. Pues que queda duplicado. Esta en la posición N y en la N-1, ya que el de la N no podemos borrarlo. Debemos llevar, aparte, un entero que nos indique cuántos amigos tenemos. Si tenemos N amigos y borramos uno, tendremos N-1, por lo que el último, el N, que está duplicado "no vale".

Una buena solución suele ser una mezcla de ambas. Para el trabajo normal suponemos, por ejemplo, que edad cero quiere decir que ese amigo no existe. Cuando queremos añadir un amigo nuevo, buscamos uno de edad cero para machacarlo encima. Si no lo hay, ponemos el amigo al final. En un momento dado, bien a petición del usuario, bien porque de vez en cuando nuestro programa decide hacerlo, se "compacta" el fichero, haciendo todos los desplazamientos necesarios para eliminar amigos inexistentes.

Ahora que hemos hecho una cosa muy complicada, vamos con una sencilla. Ficheros de texto.

Estadísticas y comentarios

Numero de visitas desde el 4 Feb 2007:

Aviso Legal