ARCHIVOS: SENTENCIAS DE E/S.












 Introducción.
El procedimiento que se sigue para acceder a la información contenida en un fichero, esto es, para leer o escribir en él, consta de tres fases:



El lenguaje C ofrece toda una gama de funciones de E/S adecuadas para el tratamiento de archivos, que se especifican en el archivo de encabezado stdio.h. Este archivo está descrito con cierto detalle en la sección correspondiente . Las funciones de este archivo de encabezado se basan en un cierto tipo de estructuras, llamadas FILE , o más exactamente en punteros de estas estructuras. De este modo, siempre que se desee hacer uso de un archivo, será preciso declarar el puntero de FILE correspondiente,

FILE * fp;

por ejemplo. Evidentemente, este puntero no tiene, en este momento, un valor definido. Para darle valor, se recurrirá a una función de apertura, encargada de reservar dinámicamente espacio para una estructura de tipo FILE . La función fopen() se encarga de realizar la operación de apertura del archivo, esto es, de preparar un flujo ( stream ) que servirá como canal de comunicación con el archivo.

Apertura
La función fopen() tiene el prototipo siguiente:
FILE *fopen( const char *nombre, const char *modo );


Esta función admite dos parámetros: la ruta de acceso al archivo en cuestión, y el modo de apertura. Evidentemente, el archivo en cuestión puede existir o no existir. Y el modo de acceso puede ser de solo lectura ( r ), solo escritura ( w ), lectura y escritura ( + ) o de adición ( a ). La función fopen() t iene el comportamiento que puede verse en la tabla siguiente:


Si el archivo... Modo r Modo w Modo a
EXISTE, fopen()
  • Proporciona un puntero válido de tipo FILE *
  • Abre el archivo en modo de sólo lectura, y sitúa el puntero de archivo en el primer byte del mismo.
  • Proporciona puntero válido de tipo FILE *
  • ¡Destruye el archivo anterior!
  • Abre el archivo en modo de sólo escritura, y sitúa el puntero de archivo en el primer byte del mismo.
  • Proporciona un puntero válido de tipo FILE *
  • Sitúa el contador de archivo al final del mismo, en el primer byte no utilizado, de modo que la información escrita siempre se añada al final del archivo.
NO EXISTE, fopen() Proporciona el puntero NULL , indicando que no ha sido posible abrir el archivo en modo de lectura.
  • Proporciona un puntero válido de tipo FILE *
  • Crea un nuevo archivo
  • Proporciona un puntero válido de tipo FILE *
  • Crea un nuevo archivo


Si se añade un " + " al modo, sea cual fuere, se abre el archivo en un modo de lectura y escritura indistintamente, mateniéndose los demás resultados. Toda apertura de un archivo debe ir equilibrada con una llamada a la oportuna función de cierre . No se puede mantener abierto un número indeterminado de archivos; existe un límite máximo dado por FOPEN_MAX que se define en stdio.h . Además, cada estructura de tipo FILE ocupa un espacio en memoria; carece de sentido tener más archivos abiertos que los estrictamente necesarios.

Posicionamiento
El control de la posición del archivo que se verá afectada por la próxima operación de lectura o escritura se basa en un puntero de archivo de tipo numérico. El valor 0 corresponde al primer byte del archivo. Es posible averiguar el valor del puntero de archivo mediante ftell() , y también se puede especificar este valor, mediante fseek() . Véanse a continuación algunas funciones relacionadas con el puntero de archivo, que nos permiten controlar archivos de manera flexible.

int fseek(FILE *fp, long offset, int modo); Esta función tiene la misión crucial consistente en situar el puntero de lectura y escritura del archivo en la posición deseada. Esta posición se mide en bytes mediante el segundo argumento; el primero es el archivo tratado y el tercero es el modo en que se contarán esos modos. Se admiten varios modos: SEEK_SET , SEEK_CUR y SEEK_END . El primer modo indica que el desplazamiento se cuenta desde el principio del archivo; el último indica que el desplazamiento se mide desde el final del archivo, y el segundo denota un desplazamiento respecto a la posición actual del archivo. Esta función es esencial ya que los procesos de lectura hacen saltar el puntero de archivo.
long ftell(FILE *stream); Esta función indica el número del próximo byte del archivo que será afectado por la próxima operación de lectura o escritura. Véanse también rewind() , fgetpos() y fsetpos() .



Escritura
Lo que sigue es una relación de funciones de uso frecuente al escribir en un archivo.

size_t fwrite( const void * ptr,
size_t longitud,
size_t num_elementos,
FILE * fp
); Esta función es la inversa de la función fread() . Su primer argumento es la dirección de la variable que se quiere escribir en disco. El segundo indica la longitud que posee cada elemento de esta variable (la función está destinada al tratamiento de listas). El tercer argumento indica el número de elementos de la tabla que se quieren escribir. El cuarto elemento es el puntero de archivo en sí.
size_t fprintf( FILE * fp,
const char * formato,
expresión_1,
expresión_,...
); Esta función es la inversa de la función fscanf() . Su primer argumento es el archivo en el cual se desea hacer la lectura. El segundo argumento es el formato con que debe hacerse ésta; el tercero son las direcciones de las variables de destino de esa información. Es preciso tener en cuenta que se respetan (al leer de archivo) las convenciones habituales de separación entre elementos (al leer de teclado).
size_t fputs( const char *,
FILE * fp
); Esta función es la inversa de la función fgets() . Su primer argumento es la cadena de origen de la información y el segundo es el archivo de escritura. Sirve para escribir cadenas completas. En este sentido resulta de especial utilidad como complemento de sprintf() Consúltese la documentación de stdio.h .


Lectura Una vez abierto un archivo existente, se puede leer su contenido. La lectura puede hacerse empleando distintas funciones, dependiendo del tipo de dato que se quiera leer; entre ellas cabe mencionar las siguientes:

int fgetc( FILE * fp ); Lee exactamente un carácter de fp , y lo proporciona en forma de int . Su valor se puede asignar a un char .
char * fgets( char * donde,
int n,
FILE * fp
); Lee n-1 caracteres de fp y los almacena en *donde . Si llega a un \n también se detiene, incluyendo el retorno. Si no lee nada, proporciona NULL. Si lee algo, proporciona un puntero de la matriz de caracteres en que se ha almacenado lo leído, donde .
int fscanf( FILE * fp,
const char * frmt,
...
); Esta función posee un comportamiento similar al de scanf() para el teclado. Su utilidad se ve reducida en el caso de que los valores alfanuméricos leídos contengan espacios; en tal caso es preciso recurrir al formato encolumnado o delimitado con el fin de disponer de un formato legible con facilidad razonable.
int fread( void * ptro,
size_t longit_elem,
size_t num_elem,
FILE * fp
); Esta función permite leer bloques de información sin interpretar su contenido; por tanto, resulta sumamente rápida. El primer argumento es un puntero que señala la variable de destino. El segundo denota la longitud de un registro del archivo. El tercero indica el número de registros presentes en el archivo. El cuarto parámetro indica el archivo en sí.
Existen otras funciones de lectura que pueden resultar interesantes en determinadas circunstancias; consúltese la documentación de stdio.h .


Cierre
Una vez realizadas operaciones de lectura o escritura, es imprescindible asegurar el correcto estado del archivo antes de abandonar la conexión obtenida mediante fopen() . Esto se hace mediante una llamada a fclose() , que realiza todas las tareas oportunas.

int fclose( FILE * fp ); Esta función vuelca a disco lo que pudiera quedar en el búfer y corta la conexión entre fp y el archivo especificado en fopen() . Consúltese la documentación de stdio.h ; puede resultar interesante la función freopen() .


Ejercicios propuestos



  1. Ejercicio 1002r01.- Construir una función que determine si un archivo existe o no. Si el archivo existe, la función no debe dejarlo abierto al salir. Si no existe, no debe crearlo.

  2. Ejercicio 1002r02.- Crear una función que escriba los números enteros del 1 al 1.000.000 en un archivo, con formato de texto. Cronometrar los tiempos mediante la orden time o similar.

  3. Ejercicio 1002r03.- Crear una función que escriba los números enteros del 1 al 1.000.000 en un archivo, con formato binario. Cronometrar los tiempos mediante la orden time o similar. Escribir los números mediante un bucle for() .

  4. Ejercicio 1002r04.- Crear una función que escriba los números enteros del 1 al 1.000.000 empleando una sola sentencia fwrite() . Cronometrar los tiempos de escritura.

  5. Ejercicio 1002r05.- Crear una función que lea los números enteros del 1 al 1.000.000 en un archivo, con formato de texto. Cronometrar los tiempos mediante la orden time o similar.

  6. Ejercicio 1002r06.- Crear una función que lea los números enteros del 1 al 1.000.000 en un archivo, con formato binario. Cronometrar los tiempos mediante la orden time o similar. Escribir los números mediante un bucle for() .

  7. Ejercicio 1002r07.- Crear una función que lea los números enteros del 1 al 1.000.000 empleando una sola sentencia fread() . Cronometrar los tiempos de escritura.

  8. Ejercicio 1002r08.- Crear una tabla en que se muestren los resultados de todas las funciones anteriores, de forma similar a la que se indica a continuación. Construir la tabla para distintos ordenadores y distintos subsistemas de disco.

    Lectura texto Lectura binario Escritura texto Escritura binario
    Uno a uno        
    Por bloques        


    Puede ser interesante repetir el experimento empleando registros en lugar de números enteros. Si se desea observar el efecto de las memorias intermedias (tanto en ordenador como en la unidad de disco), será interesante emplear números de elementos menores (para que las operaciones de lectura y escritura quepan en el búfer). ¿A partir de qué cantidad de información se supera el búfer? Comparar estos resultados con los obtenidos mediante alguna utilidad de medición de rendimientos del disco. ¿Concuerdan los resultados obtenidos con los indicados por la utilidad?