Un archivo es una colección de datos guardados en un dispositivo de almacenamiento permanente.
Aunque C# ve a los archivos como un flujo de bytes, es conveniente concebirlos como un conjunto de registros que poseen una marca de fin de archivo (eof ).
La información de un archivo se organiza en registros, los registros en campos, los campos en bytes, y los bytes en bits.
Flujos de E/S
Para que un programa pueda manejar un archivo en un dispositivo de almacenamiento permanente, como por ejemplo un disco, primero debe crearse un flujo. Un flujo es como un conducto a través del cual se transportarán los datos hacia o desde el dispositivo de almacenamiento. Los datos fluirán entre la memoria RAM de la computadora y el dispositivo de almacenamiento.
Si los datos van a enviarse desde la memoria hacia el disco , se trata de un flujo de SALIDA ( Output, en Inglés); si los datos van a enviarse desde el disco hacia la memoria , el flujo es de ENTRADA (Input, en Inglés).
A las operaciones de ENTRADA se les conoce como de LECTURA (Read, en Inglés); a las de SALIDA se les conoce como de ESCRITURA (Write, en Inglés).
En la siguiente figura se esquematiza la relación existente entre los dispositivos de Entrada/Salida y los flujos.
Tipos de archivos.
Dependiendo del tipo de datos que manejan en sus registros, los archivos se clasifican en archivos de texto y archivos binarios.
Archivos de texto.
Los datos en los archivos de texto se graban como secuencias de bytes . Por ejemplo, el dato 123456 se graba como una secuencia de 6 bytes y no como un entero, por lo que no pueden realizarse operaciones matemáticas con él.
El manejo de archivos de texto se puede llevar a cabo por medio de dos tipos de flujos: de bytes y de caracteres.
Archivos binarios.
Cuando se requiere efectuar operaciones con datos de alguno de los tipos primitivos (bool, byte, double, float, int, long, short, etc.), tales datos deberán escribirse y leerse en formato binario.
Las operaciones básicas en archivos son:
Creación
Apertura
Lectura
Escritura
Recorrido
Cierre
Archivos de texto.
El manejo de archivos de texto se puede llevar a cabo por medio de dos tipos de flujos: de bytes y de caracteres .
Archivos de Texto con Flujos de Bytes.
Para escribir o leer datos de tipo byte en un archivo se declara un flujo de la clase FileStream , cuyos constructores son:
FileStream (string nombre , FileMode modo )
FileStream (string nombre , FileMode modo , FileAccess acceso )
donde:
nombre es el nombre del archivo en disco, incluyendo la trayectoria.
Ejemplo:
"C: \\ POOISC \\ ARCHIVOS \\ archivo.txt"
o su forma equivalente:
@ "C: \ POOISC \ ARCHIVOS \ archivo.txt"
modo es un valor del tipo enumerado FileMode ; puede tomar uno de los siguientes valores:
CreateNew
Crea un nuevo archivo. Si el archivo existe, lanzará una excepción del tipo IOException.
Create
Crea un nuevo archivo. Si el archivo existe, será sobreescrito.
Open
Abre un archivo existente.
OpenOrCreate
Abre un archivo, si existe;en caso contrario, se crea un nuevo archivo.
Truncate
Abre un archivo existente y lo trunca a cero bytes de longitud.
Append
Abre un archivo para agregarle datos al final.Si el archivo no existe, lo crea.
acceso es un valor del tipo enumerado FileAccess ; puede tomar uno de los siguientes valores:
Read
Permite leer un archivo.
ReadWrite
Permite leer o escribir en el archivo.
Write
permite escribir en el archivo.
Ejemplo:
// CEscribirBytes.cs
using System;
using System.IO;
public class CEscribirBytes
{
public static void Main ( ) {
FileStream fs = null;byte[] buffer = new byte[81];int nbytes = 0, car;
try{
// Crea un flujo hacia el archivo texto.txt
fs = new FileStream("texto.txt",FileMode.Create, FileAccess.Write);
Console.WriteLine("Escriba el texto que desea almacenar en el archivo:");
while ((car = Console.Read()) != '\r' && (nbytes < buffer.Length)) {
buffer[nbytes] = (byte)car;nbytes++;
}
// Escribe la línea de texto en el archivo.
fs.Write(buffer, 0, nbytes);
}
catch(IOException e){
Console.WriteLine("Error: " + e.Message);
}
finally{
if (fs != null) fs.Close();
}
}
}
Archivos de Texto con Flujos de Caracteres.
En este caso los datos se manejan por medio de flujos de caracteres, utilizando las clases StreamWriter y StreamReader.
Escritura.
StreamWriter es una clase derivada de TextWriter.
Sus constructores son:
StreamWriter (string nombre ) // Abre un nuevo flujo para escribir en un
// archivo especificado por nombre .
StreamWriter (Stream flujo ) // Utiliza un flujo existente para escribir.
Antes de ser escritos, los datos son convertidos automáticamente a un formato portable de 8 bits (UTF-8, UCS Transformation Format 8).
Los objetos de la clase StreamWriter poseen varios métodos, entre los que destacan:
Write( ) ,WriteLine( ) y Flush( ) .
Además, poseen la propiedad BaseStream .
Ejemplo:
// CEscribirCars.cs
using System;using System.IO;
public class CEscribirCars{
public static void Main ( ){
StreamWriter sw = null;String str;
try{
// Crea un flujo hacia el archivo doc.txt// Si el archivo existe se destruye su contenido. sw = new StreamWriter("doc.txt");
Console.WriteLine("Escriba las líneas de texto aalmacenar en el archivo.\n" + "Finalice cada línea pulsando la tecla
// Lee una línea de la entrada estándar
str = Console.ReadLine();
// Mientras la cadena str no esté vacíawhile (str.Length != 0){
// Escribe la línea leída en el archivo
sw.WriteLine(str);
// Lee la línea siguiente
str = Console.ReadLine();
}
}
catch(IOException e){
Console.WriteLine("Error: " + e.Message);
}
finally{
if (sw != null) sw.Close();
}
}
}
Lectura
StreamReader es una clase derivada de TextReader y cuenta con los siguientes constructores:
StreamReader (string nombre ) abre un nuevo flujo para leer de unarchivo especificado por nombre.
StreamReader (Stream flujo ) utiliza un flujo existente para leer.
Algunos de los métodos más importantes de la clase StreamReader son:
Read( ) ,ReadLine( ) , Peek( ) y DiscardBufferData( ) .
Ejemplo:
// CLeercars.cs
using System;using System.IO;
public class CLeerCars
{
public static void Main (string[] args){
StreamReader sr = null;String str;
try{
// Crea un flujo desde el archivo doc.txtsr = new StreamReader("doc.txt");
// Lee del archivo una línea de texto
str = sr.ReadLine();
// Mientras la cadena str no esté vacíawhile (str != null){
// Muestra la línea leídaConsole.WriteLine(str);
// Lee la línea siguiente
str = sr.ReadLine();
}
}
catch(IOException e){
Console.WriteLine("Error: " + e.Message);
}
finally{
// Cierra el archivoif (sr != null) sr.Close();
}
}
}
Archivos Binarios
Cuando se requiere efectuar operaciones con datos de alguno de los tipos primitivos, tales datos deberán escribirse y leerse en formato binario.
El espacio de nombres System.IO proporciona las clases:
BinaryWriter y BinaryReader.
Con estas clases se podrán manipular datos de los tipos primitivos y cadenas de caracteres en formato UTF-8.
Los archivos escritos en formato binario no se pueden desplegar directamente en los dispositivos de salida estándar, como el monitor, sino que deben leerse a través de flujos de la clase BinaryReader.
BinaryWriter crea flujos para escribir archivos con datos de los tipos primitivos en formato binario.
Su constructor es:
BinaryWriter ( Stream flujo )
y requiere, como parámetro, un flujo de la clase Stream o sus derivadas.
Ejemplo:
FileStream fs =new FileStream("datos.dat",FileMode. Create ,FileAccess. Write );
BinaryWriter bw=new BinaryWriter( fs );
Un objeto de la clase BinaryWriter actúa como filtro entre el programa y un flujo de la clase FileStream.
En la siguiente tabla se describen algunos de los principales métodos y propiedades de la clase BinaryWriter :
Write(byte)
Escribe un valor de tipo byte.
Write(byte[])
Escribe una cadena como una secuencia de bytes.
Write(char)
Escribe un valor de tipo char.
Write(char[])
Escribe una cadena como una secuencia de caracteres.
Write(short)
Escribe un valor de tipo short.
Write(int)
Escribe un valor de tipo int.
Write(long)
Escribe un valor de tipo long.
Write(Decimal)
Escribe un valor de tipo Decimal.
Write(float)
Escribe un valor de tipo float.
Write(double)
Escribe un valor de tipo double.
Write(string)
Escribe una cadena de caracteres en formato UTF-8.El primero o los dos primeros bytes especifican el número de bytes de datos escritos en la cadena.
BaseStream
Obtiene el flujo base ( fs en el ejemplo mostrado).
Close
Cierra el flujo y libera los recursos adquiridos.
Flush
Limpia el buffer asociado con el flujo.
Seek
Establece el apuntador de Lectura/Escritura en el flujo.
BinaryReader crea flujos para leer archivos con datos de los tipos primitivos en formato binario, escritos por un flujo de la clase Binaryreader.
Su constructor es:
BinaryReader ( Stream flujo )
y requiere, como parámetro, un flujo de la clase Stream o sus derivadas.
Ejemplo:
FileStream fs =new FileStream("datos.dat",FileMode. Open ,FileAccess. Read );
BinaryReader br=new BinaryReader( fs );
Un objeto de la clase BinaryReader actúa como filtro entre un flujo de la clase FileStream y el programa.
En la siguiente tabla se describen algunos de los principales métodos y propiedades de la clase BinaryReader :
Read(byte)
Devuelve un valor de tipo byte.
Read (byte[])
Devuelve una cadena como una secuencia de bytes.
Read (char)
Devuelve un valor de tipo char.
Read (char[])
Devuelve una cadena como una secuencia de caracteres.
Read (short)
Devuelve un valor de tipo short.
Read (int)
Devuelve un valor de tipo int.
Read (long)
Devuelve un valor de tipo long.
Read (Decimal)
Devuelve un valor de tipo Decimal.
Read (float)
Devuelve un valor de tipo float.
Read (double)
Devuelve un valor de tipo double.
Read (string)
Devuelve una cadena de caracteres en formato UTF-8. El primero o los dos primeros bytes especifican el número de bytes de datos que serán leídos.
BaseStream
Obtiene el flujo base ( fs en el ejemplo mostrado).
Close
Cierra el flujo y libera los recursos adquiridos.
Flush
Limpia el buffer asociado con el flujo.
PeekChar
Obtiene el siguiente carácter, sin extraerlo.
Acceso Aleatorio.
Cuando se abre un archivo, se puede acceder a sus registros de manera secuencial o de manera aleatoria .
El acceso aleatorio consiste en posicionar el apuntador de archivo en una localidad específica del archivo. Este tipo de acceso es necesario cuando se requiere modificar alguno de los campos de un registro. En lugar de leer desde el primero hasta el registro elegido, el apuntador se posiciona ,en una especie de salto, hasta dicho registro.
El salto se hace con base al conteo de los bytes existentes entre una posición inicial y el primer byte del registro elegido.
Para el acceso aleatorio a los registros de un archivo, la clase FileStream implementa las propiedades Position y Length , así como el método Seek( )
Position devuelve la posición actual, en bytes, del apuntador de archivo .
El apuntador de archivo marca el byte donde se hará la siguiente operación de lectura o de escritura.
La declaración de la propiedad Position es:
public long Position ;
La propiedad Length devuelve la longitud del archivo en bytes. Está declarada así:
public long Length ;
El método Seek( ) mueve el apuntador de archivo a una ubicación localizada desp bytes, a partir de la posición pos del archivo.
La sintaxis para Seek( ) es:
public long Seek( long desp , SeekOrigin pos )
El desplazamiento desp puede ser positivo o negativo.
La posición pos puede tomar uno de los siguientes valores del tipo enumerado SetOrigin .
Begin ........ El inicio del archivo.
Current ..... Posición actual del apuntador de archivo.
End ........... El final del archivo.
Ejemplos:
1 .- Suponiendo un flujo fs de la clase FileStream .
fs.Seek( desp , SeekOrigin. Begin );
fs.Seek( desp , SeekOrigin. Current );
fs.Seek( -desp , SeekOrigin. End );
El valor almacenado en desp debe calcularse en base al tamaño de registro del archivo.
2 .- Suponiendo un flujo br de la clase BinaryReader .
FileStream fs = new FileStream("datos", FileMode.Open,
FileAccess.Read);
BinaryReader br = new BinaryReader( fs );
Debido a que el flujo br no soporta directamente el método Seek( ) , tiene que acceder a él a través de su propiedad BaseStream , como se muestra a continuación:
br. BaseStream.Seek (desp, SeekOrigin.Current);
Para facilitar el cálculo del desplazamiento desp , es conveniente que todos los registros tengan la misma longitud.
El desplazamiento para acceder al tercer registro se calcula de la siguiente manera:
desp = Numero de registro * tamaño de registro
desp = 2*100
Es difícil recordar el número de registro al que se necesita acceder, por lo que se hace necesario establecer una relación entre el número de registro y el valor almacenado en alguno de los campos del registro.
Por ejemplo, si cada registro tiene un campo denominado clave , de tipo entero en el rango de 1 a N (que corresponde al número de control de un empleado), la relación entre la clave y el número de registro es:
número de registro = clave -1
Así, el calculo del desplazamiento para el empleado con la clave 4 se efectúa de la siguiente manera :
desp = (4-1) * 100 = 300
Esto obliga a que, cuando se introduzcan los datos de un nuevo empleado , el número de su clave deba ser igual al número de registro +1 .
El número de registro que sigue es igual al número de registros existentes.
Bibliografia: Ceballos, Francisco Javier / sistemas.itlp.edu.mx (pagina del Tec. de la Paz).