CAPITULO 8

 

MANIPULACION DE ARCHIVOS

 

 

En algunas ocasiones nuestros programas generan información que deseamos almacenar permanentemente para tener acceso a ella cuando sea necesario. O se tienen programas que procesan volúmenes de datos tan grandes que la memoria principal del computador se hace insuficiente. Debemos entonces recurrir a un tipo de dato estructurado que nos permita almacenar su información en un dispositivo auxiliar de memoria tal como el disco duro. En el disco duro los datos se organizan en "bloques" llamados ARCHIVOS o ficheros (FILES).

 

 

 

TIPOS ARCHIVO (FILE)

 

Los datos se almacenan en un archivo como elementos individuales denominados REGISTROS, los cuales son de un mismo tipo ya sea simple o estructurado. La longitud máxima de un archivo está determinada sólo por la capacidad física del medio en el que está almacenado.

 

Mediante el APUNTADOR DEL ARCHIVO, que está señalando siempre a un registro se tiene acceso a cualquier elemento del archivo; ésto significa que mediante el apuntador podemos leer o escribir aleatoriamente sobre cualquier registro del archivo. Después de cada operación de lectura o escritura el apuntador pasará al siguiente elemento.

 

 

 

Declaración de un tipo archivo.

 

 

El tipo archivo se define mediante las palabras FILE OF seguidas por el tipo de elemento del archivo (con sus respectivos tipos si éstos no han sido predefinidos), y separados por punto y coma.

 

Ejemplo:

 

      Type

          NombreProducto = String[80];

 

          Producto = Record

               Nombre    : NombreProducto;

               Codigo    : Word;

               Costo     : Real;

               Proveedor : Integer;

          end;

 

      Var

          RegistroProductos : File of Producto;

          ListaArticulos    : File of NombreProducto;

          InfoProducto      : Producto;

          Articulo          : NombreProducto

 

 

Los componentes de un archivo pueden ser de cualquier tipo, excepto del tipo archivo.

 

 

 

Procedimientos para operaciones con archivos

 

 

La manipulación de archivos se hace a través de procedimientos estándar definidos por el lenguaje Object Pascal. A continuación se describen los procedimientos que realizan operaciones sobre archivos.

 

Para comenzar el trabajo con un archivo se debe asociar el nombre con que se identifica en el programa, con el nombre con que figura en el dispositivo de almacenamiento (el cual puede ser diferente, siguiendo las reglas para nombres de archivos dadas por el sistema operativo), de manera que toda referencia posterior sobre el identificador del archivo en el programa actuará sobre el archivo en el disco.

 

Esta acción la realiza el procedimiento

 

            AssignFile(Filvar,Str);

 

Donde el identificador Filvar es una variable de tipo archivo previamente declarada y definida como se indicó anteriormente, y Str es una cadena que contiene el nombre del archivo en el disco. Este nombre puede contener el camino de búsqueda para el archivo así:

 

      AssignFile(RegistroProductos,'C:\INVENTARIO\ARTICULO.DAT');

 

A continuación es necesario "abrir" el archivo para poder acceder sus registros, ésto se hace con los procedimientos  Rewrite o Reset.

 

Se usa Rewrite cuando se va a crear un nuevo archivo en el disco con el nombre asignado a la variable filvar, y lo prepara para procesarlo. Así:

 

            Rewrite(filvar);

 

El apuntador del archivo señala al primer registro (elemento 0) del mismo.

 

ADVERTENCIA:

Cualquier archivo existente con el mismo nombre al asignado a la variable Filvar es borrado por el procedimiento Rewrite, por lo tanto se puede usar también para "clarear" archivos ya existentes en el disco.

 

 

Cuando se van a leer o escribir registros en un archivo ya existente, sin destruír la información previa contenida en éste, se usa el procedimiento Reset. Así:

 

            Reset(filvar);

 

El procedimiento abre el archivo asignado a la variable Filvar, y lo prepara para proceso; el apuntador se sitúa al comienzo del archivo. Si el archivo no existe se producirá un error.

 

Continuando con nuestro ejemplo, y suponiendo que el archivo RegistroProductos ya existe en el disco bajo el nombre asignado (ARTICULO.DAT), si queremos actualizar el contenido de algunos de sus registros; abrimos entonces el archivo con la sentencia:

 

      Reset(RegistroProductos);

 

 

Ahora ya se pueden leer o escribir registros en el archivo por medio de los procedimientos Read y Write. A diferencia de la entrada y la salida por pantalla, la lectura y escritura de un registro en un archivo no se hace campo a campo sino que en el archivo se lee o escribe todo el registro completo.

 

 

 

            Read(filvar,var1,var2,..,varN);

 

Lee del archivo asociado a Filvar las variables indicadas en Var. Cada variable se lee desde el registro señalado y el apuntador del archivo avanza automáticamente al siguiente registro.

 

Ejemplo:

      Read(RegistroProductos,Producto);

 

Lee un registro del archivo ARTICULO.DAT y lo asigna a la variable Producto, que debe ser del mismo tipo que los registros que formen el archivo leído. Ahora bien, el registro Producto podría contener información como:

 

      Producto.Nombre    = 'Monitor Monocromatico'

      Producto.Codigo    =  21432

      Producto.Costo     =  75000.00

      Producto.Proveedor =  24

 

Información que ahora puede procesar el programa.

 

 

 

            Write(filvar,var1,var2,..,varN);

 

Escribe las variables indicadas en Var al registro señalado por el apuntador del archivo asociado a FilVar. Después de escribir cada registro, el apuntador se posiciona en el siguiente elemento.

 

 

 

Para acceder un registro específico en un archivo se usa:

 

            Seek(filvar,n);

 

Este procedimiento posiciona el apuntador señalando el n­ésimo registro del archivo filvar. La variable o expresión n debe ser entera. La posición del primer componente es 0.

 

Por ejemplo, para sobre-escribir el quinto registro de nuestro archivo de ejemplo, con los valores del registro Producto, se escribirían las siguientes sentencias:

 

      Seek(RegistroProductos,5);

      Write(RegistroProductos,Producto);

 

 

 

Una vez realizada la manipulación del archivo, sólo resta liberarlo, con el procedimiento:

 

            CloseFile(filvar);

 

El cual cierra el archivo asociado a la variable filvar y actualiza el directorio, para reflejar el nuevo estado del archivo. Se debe cerrar un archivo, aunque solamente haya sido abierto para lectura. Además si un archivo no se cierra cuando se terminan de introducir registros a éste, se pueden perder algunos datos.

 

Luego entonces, para terminar el trabajo con nuestro archivo de ejemplo escribimos la sentencia:

 

      CloseFile(RegistroProductos);

 

 

 

Otros procedimientos para el manejo de archivos son:

 

            Erase(filvar);

 

Que borra del disco el archivo asociado a la variable Filvar. Se recomienda cerrar el archivo antes de borrarlo (si éste ya ha sido abierto con alguno de los procedimientos  mencionados).

 

 

            Rename(filvar,str);

 

Renombra en el disco el archivo asociado a la variable Filvar, asignándole el nombre contenido en la cadena Str, y actualiza el directorio para reflejar el nuevo nombre del archivo. NO debe usarse Rename con archivos abiertos. Es responsabilidad del programador, verificar que no exista un archivo con el mismo nombre dado por la cadena Str, pues de ser así, éste sería sobre-escrito, perdiéndose su información original.

 

 

            Flush(filvar);

 

Transfiere toda la información del buffer interno del archivo filvar (en memoria) al archivo del disco, asegurando que la última operación de escritura sea actualizada en el disco. También asegura que la siguiente operación de lectura se haga leyendo físicamente del disco (y no leyendo del buffer en memoria). 

 

 

 

Funciones estándar para operaciones con ARCHIVOS

 

Las siguientes funciones permiten manipular archivos eficientemente:

 

 

            EOF(filvar); { End Of File }

 

Es una función Booleana que retorna TRUE únicamente si el apuntador del archivo está posicionado al final del archivo, después del último registro. En caso contrario retorna FALSE. En otras palabras nos informa del fin de un archivo, ésto es muy útil cuando se leen archivos secuencialmente con una cantidad de registros indeterminada, tal como en el ejemplo al final de esta sección.

 

 

            FilePos(filvar);

 

Es una función entera que retorna la posición actual del apuntador en el archivo filvar.

 

 

            FileSize(filvar);

 

Esta función entera retorna el número de componentes en el archivo. El primer registro es el número 0. Si  FileSize(FilVar) = 0, es porque el archivo está vacío. Así que:

 

      Seek(filvar, FileSize(filvar))

 

Coloca el apuntador del archivo FilVar al final, dejandolo listo para agregar componentes.

 

Luego, para agregar registros a nuestro archivo de ejemplo,  antes de hacer la escritura se debe posicionar el apuntador después del último registro. Esto se puede hacer con la sentencia:

 

      Seek(RegistroProductos,FileSize(RegistroProductos));

 

Ya que una vez creado un archivo en el disco, sólo se puede ampliar agregando registros al final de éste.

 

 

 

 

 

 

ARCHIVOS DE TEXTO

 

 

A diferencia de las otras clases de archivos (que son secuencias de registros con valores de cualquier tipo) los archivos de texto son básicamente caracteres organizados y estructurados en líneas,  donde cada  una  de  éstas  termina en una marca de fin de línea (EOL = End_Of_Line). Al final del archivo, despúes de la última línea o  "registro",  se  coloca una marca de fin de archivo (EOF = End_Of_File).

 

Dado que el tamaño de las líneas varía de una a otra, dentro del archivo no puede calcularse la posición de línea; por lo tanto, los archivos de texto sólo pueden procesarse secuencialmente. En este tipo de archivos no puede efectuarse simultáneamente la entrada y salida de datos .

 

 

 

Declaración de archivos de texto.

 

Una variable de tipo archivo de texto se declara haciendo referencia al tipo  estándar TextFile.

 

Así:

 

      Var

         Documento: TextFile;

 

Esta declaración le indica a Turbo Pascal que la variable Documento es un archivo de texto, que se usará más adelante.

 

Al igual que con los demás archivos, antes de usar un archivo texto (antes de efectuar cualquier operación  de entrada o salida de datos) se debe abrir, o sea hacer una llamada al procedimiento  ASSIGNFILE y una llamada a RESET o REWRITE. Para la manipulación de archivos Texto se dispone además del procedimiento APPEND, que permite abrir un archivo Texto para agregar datos al final de éste. La sintaxis es:

 

            Append(Documento);

 

 

La entrada y salida de caracteres en archivos de texto se hace mediante los procedimientos estándar READ y WRITE, o con READLN y WRITELN.

 

Ejemplos:

 

Si Documento es una variable asociada con un archivo de texto, y Var1, Var2...VarN son variables de tipo carácter o cadena de carateres, entonces:

 

      Read   (Documento,Var1,Var2...VarN);

      ReadLn (Documento,Var1,Var2...VarN);

      Write  (Documento,Var1,Var2...VarN);

 

Son sentencias válidas: en la primera instrucción se leen las variables, Var1 hasta VarN, de un archivo de texto previamente abierto y asignado a la variable Documento; La segunda instrucción hace que se lean las variables avanzando al inicio de la siguiente línea del archivo; la última sentencia, escribe las variables mencionadas en el archivo asignado a Documento.

 

 

 

Funciones para archivos texto

 

Las siguientes funciones operan con archivos texto:

 

 

            ReadLn(Documento); 

 

Salta al comienzo de la siguiente línea.

 

 

            WriteLn(Documento);

 

Escribe una secuencia CR/LF (retorno de carro y comienzo de línea -equivalente a un Enter en pantalla-) en el archivo.

 

 

            EOL(Documento);

 

Retorna verdadero si se ha alcanzado el final de línea, es decir, si el último carácter es un CR/LF o un EOF (Fin de Archivo).

 

 

            SeekEoln(Documento);

 

Es similar a EOL, excepto que esta función saltará sobre espacios en blanco y marcas de tabulación. El resultado es booleano. Util cuando se leen valores numéricos de un archivo texto.

 

 

            SeekEof(FilVar);

 

Es similar a EOF, excepto que saltará sobre espacios en blanco y tabuladores. En archivos de texto, EOF retorna TRUE si se ha alcanzado el fin del archivo (marcado como Ctrl­Z).

 

NOTA: Los procedimientos Seek y Flush y las funciones FilePos y FileSize no son aplicables a archivos texto.

 

 

 

 

El siguiente programa muestra un ejemplo del uso de archivos de texto. Este programa toma un texto fuente, tal como un archivo .PAS de Delphi y cambia todas sus mayúsculas a minúsculas, lo cual puede resultar útil si se desea una presentación más estética del código fuente en un programa. Un buen ejercicio sería ampliarlo de manera tal que haga una indentación apropiada del código fuente y tenga en cuenta detalles como colocar los identificadores de constantes en mayúsculas o no modificar las constantes de cadena.

 

 

procedure TForm1.Button1Click(Sender: TObject);

// Convierte los caracteres en mayúsculas del archivo especificado

// a minúsculas y genera un nuevo archivo con extensión '.MIN'

var

   linea            : string;

   cont             : integer;

   Mayusculas       : Set of char;

   ArchivoFuente,

   ArchivoNuevo     : TextFile;

   NombreArchivo    : String;

 

begin

   Mayusculas := ['A'..'Z'];

 

   NombreArchivo := InputBox('Archivo', 'Archivo origen', '');

   if not FileExists(NombreArchivo)

      then exit;

 

   AssignFile(ArchivoFuente, NombreArchivo);

   reset(ArchivoFuente);

 

   NombreArchivo := ChangeFileExt(NombreArchivo, '.MIN');

 

   AssignFile(ArchivoNuevo, NombreArchivo);

   rewrite(ArchivoNuevo);

 

   While not(eof(ArchivoFuente)) do

   begin

      readln(ArchivoFuente,linea);

 

      for cont := 1 to length(linea) do

          if linea[cont] in Mayusculas

             then linea[cont] := chr(ord(linea[cont]) + 32);

 

      writeln(ArchivoNuevo,linea);

   end;

 

   CloseFile(ArchivoFuente);

   CloseFile(ArchivoNuevo);

 

   ShowMessage(format('Procesado en: %s', [NombreArchivo]));

end;

 

 

 

A continuación, se lista un interesante programa, el cual codifica cualquier tipo de archivos (incluso programas ejecutables) haciéndolos inaccesibles a quien no conozca la clave de encriptación. Para volver a su estado original un archivo encriptado, basta correr de nuevo el programa usando la misma clave que se usó para realizar la codificación.

 

El método usado para realizar la codificación, es aplicar el operador bit a bit XOR entre los códigos ASCII de los caracteres que contiene el archivo y los caracteres de la cadena que sirve como "llave" de codificación.

 

Este es un metodo simple para codificar cualquier tipo de información. Un archivo codificado de esta manera es prácticamente indescifrable para el común de los usuarios pero relativamente facil de descifrar para un hacker!.

 

 

 

program Encriptador;

 

{$APPTYPE CONSOLE}

 

uses

  SysUtils;

 

var

   fuente, objeto                 : file of char;

   nombre_fuente, nombre_objeto,

   clave                          : string;

   cont                           : byte;

   carac, nuevochar               : char;

 

begin

   { TODO -oUser -cConsole Main : Insert code here }

 

   Write('Archivo fuente  : '); Readln(nombre_fuente);

   Write('Archivo destino : '); Readln(nombre_objeto);

   Write('Clave           : '); Readln(clave);

 

   AssignFile(fuente,nombre_fuente);

   AssignFile(objeto,nombre_objeto);

 

   Reset(fuente);

   Rewrite(objeto);

 

   cont := 1;

 

   While not(eof(fuente)) do

   begin

      read(fuente,carac);

      nuevochar := chr(ord(carac) xor ord(clave[cont]));

      cont := cont + 1;

 

      if cont > length(clave)

         then cont := 1;

 

      write(objeto,nuevochar);

   end;

 

   CloseFile(fuente);

   CloseFile(objeto);

end.

 

 

 

ARCHIVOS SIN TIPO

 

 

Los archivos sin tipo (Untyped files) son canales para entrada y salida (I/O) de información a bajo nivel, usados básicamente para acceder directamente cualquier archivo en disco, sin importar su tipo o estructura. Un archivo sin tipo se declara con la palabra file sin ninguna otra especificación; por ejemplo:

 

      var

            Archivo_de_datos : File;

 

Para los archivos sin tipo, los procedimientos Reset y Rewrite permiten un parámetro extra que especifica el tamaño del registro usado en las transferencias masivas de datos.

 

Por defecto, el tamaño  del registro es de 128 bytes. Pero el tamaño de registro recomendado es 1, ya que sólo este valor refleja correctamente el tamaño exacto de cualquier archivo (no son posibles registros parciales cuando la longitud del registro es 1).

 

Exceptuando Read y Write, con archivos sin tipo, puede emplearse cualquier procedimiento o función estándar. Para realizar transferencias masivas de datos a alta velocidad se usan los procedimientos BlockRead y BlockWrite en lugar de Read y Write.

 

 

            BlockRead(Archivo_de_datos, VarDatos, Num, Resultado);

 

Lee del archivo Archivo_de_datos, el número de registros especificados en Num y los almacena en la variable VarDatos, comenzando por su primer elemento (lo que permite, por ejemplo, que VarDatos sea un arreglo). En el parámetro opcional Resultado se devuelve el número de registros leídos realmente (menor o igual que Num). Si Resultado no es especificado, se genera un error cuando el número de registros leídos es diferente a Num.

 

 

            BlockWrite(Archivo_de_datos, VarDatos, Num, Resultado);

 

BlockWrite escribe en el archivo sin tipo Archivo_de_Datos el número de registros especificados en Num que se encuentran almacenados en VarDatos. El número actual de registros completos que se escribieron se devuelve en el parámetro opcional Resultado.

 

 

 

 

Estudie el siguiente ejemplo en el cual se desarrolla de nuevo el programa Encriptador , pero haciendo uso de archivos sin tipo, lo cual lo hace realmente rápido al hacerse la lectura y la escritura, no carácter  a carácter como en la versión original sino leyendo y escribiendo bloques completos de 2 Kbytes en el disco. Compare la eficiencia entre las dos implementaciones del programa.

 

 

 

Program Quick_Encrip;

// Versión mejorada del programa Encriptador, al trabajar con

// archivos sin tipo, el programa gana gran eficiencia

 

{$APPTYPE CONSOLE}

 

uses

  SysUtils;

 

var

   fuente,destino                   : File;

 

   nombre_fuente,

   nombre_destino,

   clave                            : String;

 

   cont                             : Byte;

 

   RegisLeidos,

   RegisEscritos,

   Num                              : Integer;

 

   buffer                           : Array [1..2048] of Byte;

 

begin

   { TODO -oUser -cConsole Main : Insert code here }

   Write('Archivo a encriptar :'); Readln(nombre_fuente);

   Write('Archivo encriptado  :'); Readln(nombre_destino);

   Write('Clave               :'); Readln(clave);

 

   AssignFile(fuente,nombre_fuente);

   AssignFile(destino,nombre_destino);

 

   Reset(fuente, 1);

   Rewrite(destino, 1);

 

   cont := 1;

 

   Repeat

      BlockRead(fuente, buffer, SizeOf(buffer), RegisLeidos);

 

      for Num := 1 to RegisLeidos do

          begin

               buffer[Num] := buffer[Num] xor Ord(clave[cont]);

               if cont > length(clave)

                  then cont := 1

          end;

 

      BlockWrite(destino,buffer,RegisLeidos,RegisEscritos);

   Until (RegisLeidos = 0) or (RegisEscritos <> RegisLeidos);

 

   if RegisEscritos <> RegisLeidos

      then Writeln('Error. Disco lleno?');

 

   CloseFile(fuente);

   CloseFile(destino)

 

end.