Anda di halaman 1dari 11

Manipulación de ficheros binarios y texto

Fecha: 16/Jun/2007 (14 junio 2007)


Revisado: 21/Jun/2007
Autor: José Alejandro Lugo García - jalugo@uci.cu

Introducción
¿Quién no ha necesitado el uso de ficheros al redactar un programa? ¿Quién a su vez no se ha complicado porque este y
mas cual otro lenguaje de programación le hace la vida difícil para manejar los ficheros?

El trabajo con ficheros siempre ha sido una cuestión vital dentro de todos los Entornos de Desarrollo Integrados (IDEs).
Para esto, cada lenguaje implementa de una forma u otra mediante el uso de librerías, el acceso desde nuestra
aplicación a los recursos del sistema. Las operaciones básicas sobre el uso de ficheros se consideran de lectura y
escritura, aunque también se encuentran el manejo de los atributos del fichero hasta la creación y/o eliminación de
carpetas de nuestro disco duro.

Un poco de historia
Desde que surgieron los ordenadores han sido necesarios mecanismos para guardar la información de forma persistente
en el disco duro. Los programas informáticos pueden almacenar la información en la RAM de su computadora mas al
cerrar el programa los datos que tiene en su poder se pierden si no son almacenados previamente. De aquí surge la
importancia del trabajo con ficheros, una funcionalidad que cada lenguaje de programación implementa para brindarles a
los programadores la posibilidad de manejar la información de forma más eficiente y garantizar su durabilidad.

Para quieres han programado con C++ o C, el trabajo con ficheros les traerá a la cabeza malos recuerdos (como es el
caso de quien les escribe). Demasiadas declaraciones para conformar un fichero de entrada o salida, el uso de structs o
ingenios algorítmicos, hacen del mismo una característica común para el trabajo con archivos.

Con el surgimiento de la plataforma .NET llega a nosotros la caballería pesada, una serie de novedosas funcionalidades
incluidas en un espacio de nombre que luego se hará famoso entre quienes aún, por desconocimiento o falta de apetito
en nuevos lenguajes, no conoce, me refiero a System.IO.

Como ya veremos más adelante el Framework 2.0 de .NET no se ha quedado atrás y ha propuesto para Visual Studio
2005 una mejor concepción del trabajo con ficheros tanto binarios como de texto. Pero antes, algunas definiciones
referente a estos últimos para los novatos en el asunto.

Fichero texto: Son aquellos que guardan la información en formato claro, es decir, si ud guarda la cadena string "Hola
mundo" hacia un fichero de tipo texto llamado datos.txt, cuando lo abra, verá este mismo mensaje en su interior.

Fichero binario: Son aquellos que guardan la información en formato no legible para los humanos, es decir, si ud
guarda la cadena string "Hola mundo" hacia un fichero de tipo binario llamado datos.txt, cuando lo abra, su contenido no
podrá ser leído por la persona en cuestión.

Trabajando con la información


Muchos programadores se encuentran con la necesidad de guardar largas colecciones de objetos hacia fichero y también
se enfrentan con la variante de poder trabajar con ficheros de texto o binario. Visto el epígrafe anterior se desprende el
uso que se le dé a uno u otro tipo, está claro que si no queremos que nadie pueda ver por fuera de nuestra aplicación
qué datos contiene una colección determinada, pues debemos utilizar los ficheros binarios y en caso contrario
emplearíamos los de texto. A continuación veremos cómo trabajar tanto con ficheros binarios como de texto en conjunto
con las listas de datos que podamos querer guardar en ellos, todo esto utilizando el lenguaje de programación C# desde
el IDE Visual Studio 2005 y de paso, veremos las facilidades a las que hacía mención previamente.

Ficheros binarios & Listas de Objetos


Para serializar/deserializar listas de objetos (guardarlos o abrirlos hacia/desde fichero) (Visual Studio 2005)

Debes poner arriba de tu clase la etiqueta [Serializable], o sea, si quieres guardar/cargar una lista de trabajadores, pues
debes ponerle esta etiqueta encima, quedaría así:

[Serializable]
public class Trabajador
{
private int codigo;
private string nombre;

public int Codigo


{
get
{
return codigo;
}
set
{
this.codigo = value;
}
}

public string Nombre


{
get
{
return nombre;
}
set
{
this.nombre= value;
}
}
}

Esto le indica al compilador que tu clase está lista para ser usada como tal y con eso pues puedes realizar la serializacion
de la propia clase o de Listas con tipo de esta clase. Si no le pones la etiqueta verás que no funcionará bien a la hora de
aplicarle el código que te pongo a continuación.

Para guardar una lista de objetos de este tipo de clase hacia fichero.

using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

//Guardar una lista de Objetos a Fichero

List<Trabajador> listTrab = (Método que devuelve datos de este tipo)

IFormatter formatter = new BinaryFormatter();

FileStream fs = new FileStream(saveFileDialog1.FileName,FileMode.Create);

formatter.Serialize(fs, listTrab);

fs.Close();

Para cargarlos:

using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

//Cargar una lista de Objetos desde Fichero

IFormatter formatter = new BinaryFormatter();


FileStream fs = new FileStream(openFileDialog1.FileName, FileMode.Open);

//Puedes hacerlo así


List<Trabajador> listTrab = (List<Trabajador>)formatter.Deserialize(fs);

//O puedes hacerlo así


List<Trabajador> listTrab = formatter.Deserialize(fs) as List<Trabajador>;

fs.Close();

Ficheros Texto & Listas de Objetos


Para el caso del trabajo con ficheros texto y listas de objetos, nos pudieran dar un fichero ya creado para nosotros
construir una lista de algun tipo de dato con este.

Para el caso de un fichero texto generado con este método:

StreamWriter fw = new StreamWriter(path + "Listado de Clientes.txt");

foreach (ClientePersistente CP in list)


{
fw.WriteLine(CP.NumeroCoche + "-" + CP.Nombre + "-" +
CP.Direccion + "-" + CP.Modelo);
}
fw.Close();

Cuya salida sería esta:

1-Enrique Peraza-Avenida 1ra Rpto Esperanza-Mercedes Benz


10-Mercedez Concepción-Calle 5ta, Matanzas-Audi
11-Pedro Espinosa Pérez-Santiago de las Vegas, Habana-Ferrari
2-Rosa María del Rosario-San Jose Lajas Habana-Audi
3-Mirtha Menéses-Santa Clara, Cuba-Alpha Romeo
4-Angel de la Guarda-Sueño, Santiago de Cuba, Cuba-Peugeot
5-Miguel Angel Martínez-Camajuani, VC-Audi
6-Iliana Hdez-San Juan y Martínez, Habana-Peugeot 205
7-Yumilka Diaz-Ciego de Avila-Peugeot 206
8-Yoandy Echevarría-Habana-Mercedes Benz
9-Marcheco Pérez-Habana-Niva
Como ven cada línea del fichero es un objeto, los atributos están separados en este caso por el carácter char '-'. Para
construir una lista con estos clientes donde podemos identificar como atributos del objeto el identificador, el nombre, la
dirección y el modelo de auto, podríamos hacer algo como esto:

StreamReader fr = new StreamReader(path + "Listado de Clientes.txt");

List<ClientePersistente> listCliente = new List<ClientePersistente>();

while(!fr.EndOfStream)
{
string linea= fr.ReadLine();
string[] datos = linea.Split('-');
listCliente.Add(new ClientePersistente(datos[0], datos[1],
datos[2], datos[3]));
}
fr.Close();

El split "picaría" en pedacitos una línea string. Estos pedacitos estarían delimitados por el carácter char '-' Estos
pedacitos se guardan en un arreglo de tipo string. Esto es por supuesto para el caso donde la entrada fuera de la manera
de que en cada línea sus atributos estuvieran separados por '-'. Si en lugar de estar separados por '-' estuvieran
separados por dos espacios en blanco, o cualquier cadena mas compleja que un char, o sea un string, se debe sustituir la
línea anterior del Split por esta:

//Ejemplo para atributos separados por dos espacios en blanco.


string[] datos = var.Split(new string[] {" "}, StringSplitOptions.RemoveEmptyEntries);

Para el caso de un fichero texto generado con este método:

StreamWriter fw = new StreamWriter(path + "Listado de Clientes.txt");

foreach (ClientePersistente CP in list)


{
fw.WriteLine(CP.NumeroCoche);
fw.WriteLine(CP.Nombre);
fw.WriteLine(CP.Direccion);
fw.WriteLine(CP.Modelo);
fw.WriteLine();
}
fw.Close();

cuya salida sería esta:

1
Enrique Peraza
Avenida 1ra Rpto Esperanza
Mercedes Benz

10
Mercedez Concepción
Calle 5ta, Matanzas
Audi

11
Pedro Espinosa Pérez
Santiago de las Vegas, Habana
Ferrari

2
Rosa María del Rosario
San Jose Lajas Habana
Audi

3
Mirtha Menéses
Santa Clara, Cuba
Alpha Romeo

4
Angel de la Guarda
Sueño, Santiago de Cuba, Cuba
Peugeot

5
Miguel Angel Martínez
Camajuani, VC
Audi
6
Iliana Hdez
San Juan y Martínez, Habana
Peugeot 205

7
Yumilka Diaz
Ciego de Avila
Peugeot 206

8
Yoandy Echevarría
Habana
Mercedes Benz

9
Marcheco Pérez
Habana
Niva

Haríamos nuestra lista de una manera un poco más sencilla pues no habría que usar el Split. Quedaría así:

StreamReader fr = new StreamReader(path + "Listado de Clientes.txt");

List<ClientePersistente> listCliente = new List<ClientePersistente>


();

while (!fr.EndOfStream)
{
string id = fr.ReadLine();
string nombre = fr.ReadLine();
string direccion = fr.ReadLine();
string modelo = fr.ReadLine();
fr.ReadLine();

listCliente.Add(new ClientePersistente(id,nombre,direccion,modelo));
}
fr.Close();
Posibles dudas
A continuación se exponen una serie de preguntas que pudieran convertirse en la duda de ud, amigo programador.

Pregunta 1:
En el ejemplo pusiste la variable saveFileDialog1 si yo la pongo como string me crea el archivo con ese nombre pero le
tengo que quitar el .FileName. Ahora bien mi pregunta es: ¿Que tipo de dato es el que lleva esa variable?
Respuesta:
SaveDialog es el componente que te abre la ventana Windows de Salvar un fichero y escoger la dirección donde deseas
hacerlo.
saveDialog1.FileName es la forma de obtener en un string la dirección donde guardaste el fichero con su nombre
incluido, ejemplo: C:\Documents and Settings\jalugo\Desktop\archivo.txt

Otra forma de pasarle a FileStream fs = new FileStream("paramValue", FileMode.Create) el parámetro que te puse en
negrita, por ejemplo, sería:

FileStream fs =
new FileStream("C:\Documents and Settings\jalugo\ Desktop\archivo.txt",
FileMode.Create)

pero puedes darte cuenta que para múltiples propósitos tendrías que cambiarle manualmente al código, el camino para
llegar al archivo, por tanto he utilizado la property FileName del componente SaveDialog que te devuelve en un string
todo lo necesario para guardar el archivo.

Pregunta 2:
Estoy tratando de aprender a trabajar con ficheros en c#. La cosa está en que me da un error cuando lo escribo de esta
manera:

FileStream fs =
new FileStream("C:\Documents and Settings\docencia\Desktop\archivo.txt",
FileMode.Create);

este es el error que me pone: Unrecognized escape sequence

Respuesta:
Para que no te de error eso que publicas tendrías que ponerlo así:

FileStream fs =
new FileStream("C:\\Documents and Settings\\docencia\\Desktop\\archivo.txt",
FileMode.Create);
o de esta manera:

FileStream fs =
new FileStream(@"C:\Documents and Settings\docencia\ Desktop\archivo.txt",
FileMode.Create);

Fíjate en el arroba (@) para evitar tener que poner las barras dobles... Lo que pasa con el \ es que todos los \<algo>
son "caracteres de escape", que es la manera de expresar saltos de linea, tabulador etc. con @ se le dice al compilador
que entienda esa cadena de manera literal tal y como esta, la manera de hacer que se muestre \ es el caracter de
escape \\.

Pregunta 3:
Díganme si se puede guardar un formulario completo y de poderse, por favor díganme cómo. El drama es que tengo
varias cosas en el formulario, entre los que está una matriz de PictureBox, y a la hora de serializar me dice algo como
que la clase PictureBox no puede ser serializada. Si alguien tiene una idea de cómo puedo hacerlo...

Respuesta:
La solución está en poner todos los posibles tipos de datos que contenga tu formulario hacia un objeto y le pongas
encima la etiqueta Serializable tal y como se explicó anteriormente. Para el caso del PictureBox la imagen que contiene
es de tipo Image y la misma es un tipo de dato serializable por tanto no hay problema en que la puedas guardar
conjuntamente con los otros tipos de datos que puedan haber (string, datetime, etc). Tu clase quedaría así:

[Serializable]
public class Datos
{
string text1, text2;
DateTime dt;
Image img;
public Datos(string t1, string t2, DateTime dt, Image img)
{
this.text1 = t1;
this.text2 = t2;
this.dt = dt;
this.img = img;
}

public string Text2


{
get { return text2; }
set { text2 = value; }
}
public string Text1
{
get { return text1; }
set { text1 = value; }
}

public DateTime Dt
{
get { return dt; }
set { dt = value; }
}

public Image Img


{
get { return img; }
set { img = value; }
}
}

Si me preguntas como sé que Image es serializable, con el proyecto abierto desde el Visual Studio, da clic derecho
encima de Image y selecciona "Go To Definition", verás que la clase implementa la interfaz "ISerializable". Resumiendo,
utilizando la función Serialize y Deserialize se pueden guardar tipos de datos (con la etiqueta Serializable) que engloben
otros tipos de datos serializables como es el caso de Image.

Pregunta 4:

Tengo un problema con mi fichero. Yo al inicio de mi programa le pido al usuario que entre su nick y contraseña los
cuales estan guardados en un fichero y lo que hago es comprobar si estos datos son correctos. Hasta aqui no tengo
problemas. El problema surge cuando al yo adicionar usuarios a mi fichero se me borran los anteriores que tenia. ¿Qué
puedo hacer?

Respuesta:

Debes comprobar si estás abriendo el fichero en modo Create (FileMode.Create), caso con el cual te sobreescribe el
fichero y por supuesto te borra lo que tenías anteriormente. Para solucionar esto lo debes poner en FileMode.Append,
para que te ponga los nuevos datos al final del fichero que ya tienes creado...

Conclusiones
El trabajo con ficheros texto al igual que con binarios es una tarea que resulta relativamente fácil para Visual C# 2005.
Hemos visto cómo construir tanto ficheros textos como binarios que representen listas de objetos al igual que hemos
aprendido a revertir el proceso, leyendo de los mismos y volviendo a construir dichas listas.
Espacios de nombres usados en el código de este artículo:

System.Runtime.Serialization - para el IFormatter


System.Runtime.Serialization.Formatters.Binary - para el BinaryFormatter

Código de ejemplo (comprimido):

Fichero con el código de ejemplo: jalugo_trabajo_ficheros_CSharp.zip - 14.20 KB

(MD5 checksum: FA99DFE045AA56CD875F120162D4F043)