Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Ficheros
Ficheros
2
CONCEPTOS BSICOS..........................................................................................................................2
RUTAS DE FICHEROS Y DIRECTORIOS....................................................................................................3
SINTAXIS EN WINDOWS........................................................................................................................3
RUTAS INDEPENDIENTES DEL SISTEMA OPERATIVO.....................................................................................4
OPERACIONES CON RUTAS.....................................................................................................................5
REPRESENTACIN DE FICHEROS Y DIRECTORIOS EN .NET......................................................................6
ELEMENTOS COMUNES..........................................................................................................................6
ELEMENTOS ESPECFICOS DE DIRECTORIOS...............................................................................................9
ELEMENTOS ESPECFICOS DE FICHEROS..................................................................................................10
FLUJOS DE ENTRADA-SALIDA.............................................................................................................11
LECTURA Y ESCRITURA.......................................................................................................................12
MOVIMIENTO POR EL FLUJO................................................................................................................12
VOLCADO DE DATOS EN FLUJOS...........................................................................................................14
CIERRE DE FLUJOS.............................................................................................................................14
FLUJOS DE ENTRADA-SALIDA EN FICHEROS..........................................................................................14
CREACIN DE OBJETOS FILESTREAM....................................................................................................14
MANIPULACIN DEL CONTENIDO DE LOS FICHEROS..................................................................................16
CONTROL DE CONCURRENCIA EN ACCESOS A FICHEROS............................................................................20
ACCESO NATIVO A FICHEROS...............................................................................................................20
FICHERO BINARIOS...........................................................................................................................21
FICHEROS DE TEXTO.........................................................................................................................22
LECTURA DE FICHEROS DE TEXTO.........................................................................................................22
ESCRITURA EN FICHEROS DE TEXTO.....................................................................................................25
MANIPULACIN DEL SISTEMA DE ARCHIVOS........................................................................................27
MANIPULACIN DE FICHEROS..............................................................................................................27
MANIPULACIN DE DIRECTORIOS..........................................................................................................29
MANIPULACIN DE RUTAS..................................................................................................................30
DETECCIN DE CAMBIOS EN EL SISTEMA DE ARCHIVOS.........................................................................30
SELECCIN DE FICHEROS A VIGILAR......................................................................................................31
SELECCIN DE CAMBIOS A VIGILAR.......................................................................................................32
DETECCIN SNCRONA DE CAMBIOS......................................................................................................32
DETECCIN ASNCRONA DE CAMBIOS....................................................................................................34
EJEMPLO: CIFRADOR DE DIRECTORIOS..................................................................................................35
PROBLEMAS DE DESBORDAMIENTOS DEL BUFFER DE CAMBIOS...................................................................36
FICHEROS TEMPORALES....................................................................................................................37
Tema 3: Ficheros
Conceptos bsicos
Un fichero puede verse como una porcin de un dispositivo de almacenamiento no
voltil (disco duro, disquete, etc.) a la que se le asocia un determinado nombre, estando
en principio la cantidad de datos que puede almacenar slo limitada por la cantidad de
espacio del que disponga en cada momento el dispositivo donde se almacenen esos
datos o por las caractersticas del cada sistema operativo. Por no voltil se entiende que
a diferencia de lo que ocurre con otros almacenes de datos como la memoria RAM, la
informacin en ellos almacenadas no se pierde al apagarse el ordenador.
Dada la importancia de los ficheros como almacenes no voltiles de la informacin, la
BCL incluye todo un espacio de nombres llamado System.IO especialmente orientado al
trabajo con ellos. En este tema se realizar un estudio en profundidad del mismo y se
explicar cmo se pueden aprovechar los servicios que sus tipos ofrecen para facilitar la
manipulacin de los mismos. Por ello, salvo que se indique explcitamente lo contrario
puede considerar que todos los nuevos tipos aqu citados forman parte de dicho espacio.
Aunque cada sistema operativo puede tener su propio formato de nombres de fichero,
nosotros veremos el que se utiliza en la plataforma .NET, que bsicamente puede
considerarse que consiste en nombrar a los ficheros usando hasta 259 caracteres
Unicode imprimibles e interpretndose el nombre que se les d segn este formato:
<nombre>.<extensin>
<nombre> indica cul ha de considerarse que es en realidad el nombre del
<extensin> se indica cul es el tipo de fichero del que se trata. La idea es
fichero y en
que ficheros
con el mismo tipo de contenido tengan una extensin comn para que as sea ms fcil
identificar su contenido, por lo que lo que distinguir a unos ficheros de un tipo de otros
de su mismo tipo ser su <nombre> As, los ficheros de texto generados por Microsoft
Word tienen extensin doc, las compresiones con Winzip tienen extensin zip, etc.
Los ficheros se agrupan en directorios o carpetas, que pueden verse simplemente
como nombres comunes bajo los que se agrupan conjuntos de ficheros relacionados
entre s. Cada directorio pueden contener a su vez otros directorios, lo que hace que el
sistema de archivos adquiera una estructura jerrquica donde cada fichero o directorio
tiene como padre al directorio en que est contenido. Obviamente, para que sta sea una
estructura finita habr de existir un directorio raz que contenga a todos los dems y
no est contenido dentro de ninguno otro.
La utilidad de los directorios es doble:
1. Permiten organizar el sistema de archivos del ordenador de manera que sea ms
fcil localizar ficheros en l, pues evitan tener que buscarlos entre todos los ficheros
de la mquina y acotar las bsquedas tan slo a los incluidos en ciertos directorios.
2. Evitan conflictos de nombres, pues si cada aplicacin instala sus ficheros en un
directorio propio podrn coexistir en una misma mquina varios ficheros con el
Como cada directorio puede estar contenido dentro de otro directorio, lo que se indica
en <nombreDirectorioPadre> es a su vez la ruta completa del directorio donde se
encuentra el fichero indicado en <nombreFicheroODirectorio>, y obviamente el primer
carcter de cualquier ruta completa ser el \ correspondiente al directorio raz. Por
ejemplo, la ruta completa de un fichero datos1.dat incluido dentro de un directorio
llamado Datos es \Datos\datos.dat, pero si dicho directorio se encontrase a su vez
contenido dentro de Programa entonces sera \Programa\Datos\datos.dat
Si al indicar el nombre de un fichero no se diese su ruta completa se considerara que la
ruta especificada es una ruta relativa a la posicin actual en el rbol de directorios. Es
decir, que se trata de una ruta en la que <nombreDirectorio> ha de considerarse que es el
directorio desde el que se la hace referencia al fichero. Por tanto, si desde el directorio
\Programa se quisiese hacer referencia al fichero datos.dat del ejemplo anterior
bastara indicar Datos\datos.dat, pero para hacerle referencia desde el directorio
\Programa\Datos bastara indicar datos.dat.
Adicionalmente a la sintaxis vista, en sistemas operativos como Windows donde se
puede trabajar con mltiples unidades de disco a cada una de las que se les asocia un
nombre diferente, la sintaxis anterior para las rutas completas se ve ampliada as:
<nombreUnidad>:<nombreDirectorio>\<nombreFichero>
Tabla 1: Campos de Path independizadores del formato de rutas de los sistemas operativos
Usar el campo independizador del sistema operativo, aunque ello tiene el problema
de que da lugar a cdigos poco compacto. Por ejemplo, para la ruta anterior habra
que representarla con c:+Path.DirectorySeparatorChar+\datos.
Duplicar los caracteres \ de los literales para que dejen de considerarse secuencias de
escape. As, la ruta de ejemplo anterior quedara como c:\\datos
Dicho esto, el primer grupo de mtodos que veremos son los destinados a facilitarnos la
extraccin de informacin sobre las diferentes partes de la ruta que se les pase, que son:
Aparte de estos mtodos tambin se incluyen otros que permiten hacer tareas de
diversos tipo relacionadas con rutas como:
Cuando use cualquiera de estos mtodos tenga siempre presente una cosa: permiten slo
operar con cadenas de texto que representan rutas pero no estn de en ninguna manera
relacionados con las ruta que representan. Su utilidad es permitir preparar nuevas rutas a
partir de otras ya existentes para luego poder, si se desea, podrn ser pasarse a otros
mecanismos para actuar fsicamente sobre ellas. Por ejemplo, modificar una ruta con
ChangeExtension() no implica que la ruta fsica en el dispositivo no voltil al que est
asociada quede tambin modificada, pero la cadena que se devuelva puede usarse luego
para modificarla mediante otros mtodos que iremos viendo a lo largo del tema.
Representacin de ficheros y directorios en .NET
Elementos comunes
En la plataforma .NET el trabajo con ficheros y directorios se hace, como no, siguiendo
un enfoque orientado a objetos en el que cada se los encapsula dentro de un objetos de
ciertos tipos: los ficheros en objetos FileInfo y los directorios en objetos DirectoryInfo.
Dado que ambos tipos de elementos dispone de muchas caractersticas , lo que se ha
hecho en la BCL es definir los tipos envolventes que los representan en base a una clase
abstracta comn llamada FileSystemInfo que facilite la escritura de cdigo genrico que
al slo usar sus caractersticas comunes pueda trabajar tanto con unos como con otros.
Segn su funcionalidad los miembros comunes ofrecidos por esta clase se agrupan en:
Borrado: Como tanto los ficheros como los directorios pueden ser borrados, en
FileSystemInfo se proporciona un mtodo Delete() mediante el que es posible borrar
ambos tipos de elementos del sistema de archivos. Sin embargo, al usarlo hay que
tener en cuenta que si el elemento a borrar es un directorio, ste ha de estar vaco
porque si no lanzar una IOException
Por otro lado, los sistemas operativos tambin suelen asociar una serie de atributos a
cada uno de sus ficheros y directorios que describen cmo ha de interpretarse bajo
determinadas circunstancias. Mediante la propiedad Attributes podemos acceder a
ellos tanto para leerlos como para modificarlos. Esta propiedad es de un tipo
enumerado llamado FileAttributes que admite los siguientes literales:
Literal
Normal
Directory
Hidden
System
ReadOnly
Encrypted
Compressed
Temporary
Archive
Offline
NotContentIndexed
SparseFile
ReparsePoint
Tenga en cuenta que la tabla anterior abarca atributos propios de muy diversos
sistemas operativos que no tienen porqu estar presentes en todos los sistemas
operativo. Adems, aunque pueda darle la sensacin de que cada fichero o directorio
slo puede disponer de slo uno de los atributos sealados porque su propiedad
Attributes devuelve un nico objeto, esto no es as porque FileAttributes ha sido
definido como una enumeracin de flags y sus objetos pueden almacenar
combinaciones OR de sus literales. Por ejemplo, un mtodo que convierta ficheros o
directorios en ocultos y de slo lectura puede escribirse as:
public void ConvierteEnOcultoDeSoloLectura(FileSystemInfo f)
{
f.Attributes = FileAttributes.Hidden | FileAttributes.ReadOnly;
}
Aunque el trabajo con ficheros y con directorios es muy similar de hecho, como ya
hemos visto, los tipos que los representan derivan de una clase comn FileSystemInfo-,
tambin es cierto que cada uno de ellos dispone tiene una serie de caractersticas propias
que lo diferencian del otro. Por esta razn, en la BCL se ha definido como abstracta
FileSystemInfo y se han incluido subclases suyas no abstractas FileInfo y DirectoryInfo
que aaden a los miembros comunes heredados de su padre miembros relacionados con
funcionalidades particulares de cada tipo de elemento.
Elementos especficos de directorios
Para crear el objeto DirectoryInfo que represente a un determinado directorio basta pasar
como parmetro de su constructor una cadena con la ruta relativa o completa- del
mismo. Por ejemplo, un objeto d que represente al directorio c:\Pruebas se crea as:
DirectoryInfo d = new DirectoryInfo(@c:\Pruebas);
Este directorio no tiene porqu existir, pues como se ver ms adelante es posible crear
directorios a travs de objetos DirectoryInfo que representen directorios no existentes.
Como ya se ha dicho, este objeto es en realidad de una subclase de FileSystemInfo y por
tanto dispondr de todos los miembros definidos en ella adems de los explcitamente
definidos en DirectoryInfo. Estos miembros adicionales le aportan las funcionalidades
especficas del trabajo con directorios descritas a continuacin.
Informacin adicional sobre el directorio
A la informacin sobre un directorio que proporciona FileSystemInfo (nombre, fechas,
atributos, etc.), DirectoryInfo aade los siguientes datos:
Cada uno de estos mtodos tiene una sobrecarga que admite como parmetro una
cadena con la que pueden filtrarse los nombres de los ficheros o directorios que se
desean obtener. Dicho filtrado consiste en usar los caracteres ? y * como comodines con
Delete():
MoveTo(string directorioDestino):
A travs de este objeto se pueden hacer las operaciones tpicas de creacin (Create()),
borrado (Delete()) y movimiento (MoveTo()) de ficheros de manera anloga a como se ha
visto que se hacen con los directorios. Adicionalmente se ha aadido un mtodo
CopyTo() que funciona de manera similar a MoveTo() slo que en vez de cambiar al
ubicacin del fichero lo que hace es crear una copia del mismo en la ubicacin indicada.
De nuevo, para evitar sobreescrituras no deseadas ambos mtodos no admiten que el
fichero de destino especificado ya exista lanzan IOExceptions si fuese as, pero ello
tambin puede cambiarse pasndoles true como segundo parmetro.
En lo referente al acceso a informacin adicional sobre el fichero hay que sealar que en
este caso la analoga con el caso de los directorios no es tan pura, pues para obtener el
directorio padre de un fichero la propiedad a usar del objeto FileInfo que lo representa
no se llama ahora Parent sino DirectoryName. Adems, tambin se ofrece una segunda
propiedad llamada Directory que permite acceder a esa misma informacin pero en
forma de objeto DirectoryInfo en lugar de cadena de texto.
Como habr adivinado, aparte de estos miembros los objetos FileInfo deben tambin de
ofrecer mecanismos que permitan acceder al contenido de los ficheros que representan,
ya sea para leerlo o para modificarlo. Pues bien, precisamente a explicar cmo se realiza
eso es a lo que esta destinado los siguientes epgrafes.
Flujos de entrada-salida
La forma con la que se trabaja con ficheros en la plataforma .NET est ntimamente
ligada al concepto de flujo de entrada-salida, que consiste en tratar su contenido como
si de una secuencia ordenada de bytes se tratase. Este concepto no est ligado en
exclusividad a los ficheros, sino que es un concepto abstracto aplicable a otros tipos de
almacenes de informacin tales como conexiones de red o buffers en memoria.
En realidad se distinguen dos tipos de flujos:
Flujos base: Trabajan directamente con algn tipo de medio fsico, como puede ser
una porcin de memoria, de espacio en disco o una conexin a red.
Flujos intermedios: No trabajan con medios fsicos directamente sino envuelven a
otros flujos a los que proporcionan diferentes caractersticas, pudindose combinar
entre s de manera que el flujo base que haya tras ellos pueda verse beneficiado por
todas las funcionalidades que ofrezcan todas ellos. Ejemplos son los flujos que
proporcionan encriptacin de datos o buffers de almacenamiento intermedio.
En la BCL todos los tipos relativos al trabajo con flujos se encuentran agrupados dentro
del espacio de nombres System.IO Un tipo muy importante entre ellos es Stream, que es
la clase abstracta base de todos los flujos y les proporciona los miembros bsicos que
permiten trabajar con ellos. Estos miembros se explicarn en los epgrafes que siguen.
Lectura y escritura
Como es lgico, todo flujo dispondr de mecanismos mediante los que se le puedan
extraer y aadir bytes. No todos los flujos tienen porqu admitir ambas operaciones, por
lo que para saber qu operaciones admite cada flujo concreto Stream aade a todos ellos
dos propiedades bool CanRead y bool CanWrite que permite consultarlo.
Para la lectura se ofrecen mtodos ReadByte() y Read() que, respectivamente, permiten
extraerles uno o varios bytes (se almacenaran en una tabla byte[]); y para la escritura se
hace lo mismo con WriteByte() y Write(). Obviamente los mtodos de lectura slo sern
aplicables flujos que admitan lectura y los de escritura slo sern aplicables a los que
admitan escritura, y si se intentan aplicar a flujos que no los admitan se lanzarn
excepciones NotSupportedException.
Los mtodos anteriores funcionan sncronamente, lo que significa que tras llamarlos el
cdigo que los llam se quedar en espera de que terminen de ejecutarse. Sin embargo,
como las operaciones de entrada-salida suelen ser lentas tambin se ofrecen parejas de
mtodos BeginRead()-EndRead() y BeginWrite()-EndWrite() que permiten realizarlas
asncronamente de manera que mientras se estn realizando el cdigo llamador pueda
seguir ejecutndose y realizando otras tareas. Estos mtodos funcionan anlogamente a
como lo hacen las parejas de mtodos BeginInvoke y EndInvoke de los delegados.
Tras cada lectura la siguiente llamada a estos mtodos devolvera los bytes del flujo
siguientes a los ltimos ledos, y para detectar cuando se alcance el final del flujo basta
mirar su valor de retorno, pues en ese caso ReadByte() devolver un 1 y Read() un 0.
Respecto a la escritura, cada vez que se escriba se escribir a continuacin de los
ltimos bytes escritos en el flujo.
Movimiento por el flujo
Por defecto, la primera operacin que se realice sobre un flujo se aplica a su comienzo y
las siguientes se aplican tras la posicin resultante de la anterior. Es decir, en cada
lectura o escritura se lee o escribe a continuacin de la ltima posicin accedida, por lo
que antes de acceder a una determinada posicin habr que pasar antes por las previas.
A esto se le conoce como acceso secuencial.
Sin embargo, hay situaciones en las que puede resultar interesante poderse escribir o
leer de cualquier posicin del flujo sin necesariamente tener que pasar antes por sus
anteriores. A esto se le conoce como acceso aleatorio, y como no todos los flujos tienen
porqu admitirlo, cada flujo dispone de una propiedad de slo lectura bool CanSeek que
indica si lo admite.
Si un flujo admite acceso aleatorio le sern aplicables los miembros de Stream relativos
a dicho tipo de acceso que a continuacin se muestran, mientras que si no lo admite la
utilizacin de los mismos provocar excepciones NotSupportedException:
long Length {get;}: Nmero de bytes almacenados en el flujo (tamao del
flujo)
Este mtodo imprime en la consola el byte que se le indique del flujo que se le
pase como parmetro. Ntese que, para asegurar que su ejecucin sea inocua y
no altere el flujo se controla a su comienzo cul era la posicin inicial en el flujo
y a su final se le restaura a dicha posicin. Por otra parte, tambin se controla
que el nmeroByte indicado se encuentre dentro del flujo, pues sino aunque la
asignacin flujo.Position = nmeroByte sera vlida y no producira excepciones, el
flujo habra quedado en un estado inconsistente y ReadByte() devolvera 1.
long Seek(long posicin, SeekOrigin inicio): Permite colocarnos en un
byte del flujo determinado. La posicin de ste se indica de manera relativa
respecto a la posicin de inicio sealada por su segundo parmetro, que es de un
tipo de enumeracin cuyos posibles literales son Current (posicin actual en el
flujo), Begin (inicio del flujo) y End (final del flujo) Por ejemplo:
// Coloca justo antes del comienzo del flujo
flujo.Seek(0, SeekOrigin.Begin);
// Coloca justo despus del final del flujo
flujo.Seek(0, SeekOrigin.End);
// No nos movemos de la posicin actual
flujo.Seek(0, SeekOrigin.Current);
// Coloca dos bytes a continuacin de la posicin actual en el flujo
flujo.Seek(2, SeekOrigin.Current);
// Coloca dos bytes antes de la posicin actual en el flujo
flujo.Seek(-2, SeekOrigin.Current);
// Coloca en el segundo byte del flujo
flujo.Seek(2, SeekOrigin.Begin);
OpenOrCreate
CreateNew
Create
Truncate
Append
Modo de apertura
Abre el fichero presuponiendo existe. Si no fuese as se lanza una
FileNotFoundException.
Si el fichero no existe, lo crea
Abre el fichero presuponiendo que no existe y crendolo. Si
existiese se lanzara una IOException
Crea el fichero, y si ya exista lo sobreescribe
Abre el fichero presuponiendo que existe y borrando todo su
contenido. Si no existiese se lanzara una FileNotFoundException
Abre el fichero en modo de concatenacin, lo que significa que
slo podr escribirse a su final. Si el fichero no existe, lo crea.
Por ejemplo, para abrir un fichero preexistente llamado c:\datos.dat puede hacerse:
FileInfo fichero = new FileInfo(@c:\datos.dat);
FileStream contenidoFichero= fichero.Open(FileMode.Open);
Fjese que algunos modos de aperturas de ficheros, por su propia definicin, requieren
de ciertos permisos de acceso. Por ejemplo, un fichero slo puede abrirse en modo de
concatenacin (FileMode.Append) si se tiene permiso de acceso FileAccess.Write; y ni
siquiera se permite abrirlo si se tiene FileAccess.ReadWrite porque es absurdo leer de l.
Un ejemplo de cmo abrir un fichero preexistente en modo de slo lectura es:
FileInfo fichero = new FileInfo(@c:\datos.dat);
FileStream contenidoFichero= fichero.Open(FileMode.Open, FileAccess.Read);
Ahora bien, como es bastante frecuente abrir en modo de slo lectura o de lecturaescritura ficheros preexistentes, a los objetos FileInfo tambin se les ha dotado de un par
de mtodos FileStream OpenRead() y FileStream OpenWrite() que permiten, de manera
respectiva, abrir as los ficheros que representan. Por tanto, en el ejemplo anterior en
realidad podra haberse escrito de la siguiente manera mucho ms compacta:
FileInfo fichero = new FileInfo(@c:\datos.dat);
FileStream contenidoFichero= fichero.OpenRead();
Por otra parte, la mayora de los sistemas operativos permiten controlar la forma en que
diferentes procesos puede acceder simultneamente a sus ficheros. Por seguridad, para
evitar problemas de concurrencia no se permite que mltiples hilos puedan compartir
ficheros abiertos con cualquiera de las sobrecargas de Open() vistas. Sin embargo, ello
puede cambiarse especificando un tercer parmetro de tipo enumerado FileShare, cuyos
posibles literales son:
Literal de FileShare
None
Read
Write
ReadWrite
Modo de comparticin
No se puede compartir
Puede compartirse con hilos que slo quieran leerlo
Puede compartirse con hilos que slo quieran escribir en l
Puede compartirse con cualquier otro hilo
Hasta ahora todas las formas de crear objetos FileStream que hemos visto eran mtodos
proporcionados por la clase FileInfo que encapsula ficheros, lo que nos obliga a siempre
tener que crear un objeto de este tipo para poder acceder al contenido de un fichero.
Como ello puede resultar ineficiente, la propia clase FileStream tambin proporciona
una familia de constructores que permiten crear objetos suyos mucho ms directamente.
As, los siguientes constructores son equivalentes a las tres sobrecargas de Open() vistas:
FileStream(string rutaFichero, FileMode modoApertura)
FileStream(string rutaFichero, FileMode modoApertura, FileAccess modoAcceso)
FileStream(string rutaFichero, FileMode modoApertura, FileAccess modoAcceso,
FileShare modoComparticin)
Por ejemplo, para leer el contenido del fichero c:\datos.dat podemos generar un
FileStream as:
FileStream contenidoFichero = new FileStream(@c:\datos.dat, FileMode.Open,
FileAccess.Read);
En principio los ficheros admiten tanto lectura como escritura y movimiento por su
contenido, por lo que las propiedades CanRead, CanWrite y CanSeek de los objetos
FileStream valdrn siempre true y los mtodos ReadByte(), Read(), WriteByte(), Write() y
Seek() heredadas de Stream sern plenamente utilizables. Sin embargo, como es obvio,
esto depender de cmo hayamos abierto el fichero, pues si por ejemplo lo abrimos en
modo slo lectura, CanWrite devolver false y mtodos como WriteByte() o Write()
dejarn de funcionar y provocarn NotSupportedExceptions.
Dada la relativa lentitud de las operaciones de acceso a ficheros, para mayor eficiencia
durante su realizacin se ha optado por asociar un buffer a cada FileStream en el que los
bytes que se soliciten escribir se irn almacenando hasta alcanzar un cierto nmero para
entonces escribirlos todos de una vez. Por defecto el tamao de este buffer es de 8192
bytes, pero puede cambiarse usando el siguiente constructor para crear el FileStream:
FileStream(string rutaFichero, FileMode modoApertura, FileAccess modoAcceso,
FileShare modoComparticin, int tamaoBuffer)
Al especificar as un tamao de buffer hay que tener en cuenta que no conviene darle un
tamao inferior a 8 bytes, pues sera tan pequeo que prcticamente no se ganara nada
de eficiencia. Es ms, cualquier valor inferior a dicha cantidad que se le d ser
ignorado y se tomar en su lugar un tamao de 8 bytes.
Lo que si es importante recordar es que debido a este buffer, cuando queramos estar
seguros de que en un momento concreto se haya volcado todo su contenido en el fichero
al que est asociado, hemos de llamar al mtodo Flush() Por cierto, por si lo estaba
pensando: cuando hayamos terminado de usar un FileStream y vayamos a cerrarlo no
tenemos porqu preocuparnos de vaciar este buffer, pues el mtodo Close() usado para
ello se encarga automticamente de hacerlo.
Ntese que hasta ahora no se ha dicho nada acerca de las versiones asncronas de los
mtodos de lectura-escritura heredados de Stream. Esto se debe a que por defecto el
acceso al contenido de los ficheros se realiza siempre de manera sncrona, aunque se
usen los mtodos anteriores. Esto se debe a que para ficheros pequeos es la forma de
acceso ms eficiente, que son los ms frecuentemente utilizados. Sin embargo, como al
trabajar con ficheros grandes puede resultar ms eficiente leerlos asncronamente para
poder realizar otras tareas mientras tanto, tambin se permite activar el acceso
asncrono. Para ello basta crear el FileStream usando el siguiente constructor:
FileStream(string rutaFichero, FileMode modoApertura, FileAccess modoAcceso,
FileShare modoComparticin, int tamaoBuffer, bool asncrono?)
Lock(long byteInicial, long nBytes): Bloquea el acceso a la seccin del fichero que
comienza en el byteInicial indicado y cuyo tamao en bytes es nBytes
Unlock(long byteInicial, long nBytes): Desbloquea el acceso a la seccin del fichero
primer constructor considera que lo es, y si no desea ocurra esto basta con que utilice
cualquiera de los dems constructores dndole el valor false a dicho parmetro.
Fichero binarios
La forma de acceder a ficheros que hasta ahora hemos visto tiene una seria limitacin, y
es que trabaja con la informacin a nivel de bytes, lo cual no suele ser muy conveniente
en tanto que generalmente en las aplicaciones no suele trabajarse directamente con bytes
sino con objetos ms complejos formado por mltiples bytes. Por ejemplo, dado que un
int ocupa 4 bytes, para almacenar su valor en un fichero usando un FileStream habra
que realizar una operacin WriteByte() por cada uno de sus bytes o descomponer todos
los en una tabla byte[] y escribirlos de una vez con una llamada a Write()
Como esta no es ni por asomo una solucin cmoda, en System.IO se han incluido un
par de tipos llamados BinaryReader y BinaryWriter que encapsulan FileStreams y les
proporcionan una serie de mtodos con los que se simplifica la lectura y escritura de
objetos de cualquier tipo en ficheros. Aunque similares a flujos intermedios, no hay que
confundir estos tipos con tales, pues aunque encapsulan a otros flujos y disponen de
muchos de los mtodos comunes a stos, no derivan de Stream.
Para crear objetos de estos tipos simplemente basta con pasar como parmetro de sus
respectivos constructores el flujo a envolver, pues stos son de la forma:
BinaryReader(Stream flujo)
BinaryWriter(Stream flujo)
Una vez encapsulado el flujo siempre es posible extraerlo mediante la propiedad Stream
BaseStream {get;} que ambos tipos de objetos proporcionan para ello.
Los BinaryWriters se caracterizan por disponer de un mtodo Write() con mltiples
sobrecargas que toman parmetros de cualquiera de los tipos bsicos excepto object y
los escriben directamente en el flujo que encapsulan.
Anlogamente, los BinaryReaders disponen de una familia de mtodos ReadXXX() (int
ReadInt(), string ReadString(), etc.) que permiten leer del flujo cualquier tipo bsico.
Adems, adicionalmente se les ha aadido un mtodo int PeekChar() que permite
consultar el siguiente carcter del flujo en forma de int (o un 1 si no quedasen ms)
pero sin extraerlo del mismo, de forma que la siguiente lectura se realizara como si la
consulta nunca hubiese sido realizada. Sin embargo, quizs por error en el diseo de la
beta 2, no disponen de un mtodo Seek() anlogo al que tienen los BinaryWriters2.
Por defecto, se considera que la codificacin de las cadenas de texto que se lean o
escriban del flujo es UTF-8, pero puede especificarse cualquier otra usando las
siguientes sobrecargas de sus respectivos constructores:
BinaryReader(Stream flujo, Encoding codificacin)
BinaryWriter(Stream flujo, Encoding codificacin)
De todas formas fjese que puede seguir haciendo accesos aleatorios a travs del mtodo Seek() del
objeto que encapsula, pues ste puede recuperarse a travs de la propiedad BaseStream antes comentada
Por ejemplo, para escribir en un flujo que represente a un fichero llamado datos.dat
usando UTF8 al codificar los caracteres puede hacerse lo siguiente:
FileStream fichero = new FileStream(datos.dat, FileMode.Create);
BinaryWriter ficheroBinario = new BinaryWriter(fichero, Encoding.UTF8));
ficheroBinario.Write(Este mensaje se escribir en UTF8 dentro de datos.dat);
Del mismo modo, para leer correctamente dicho mensaje habra que hacer algo como:
FileStream fichero = new FileStream(datos.dat, FileMode.Open);
BinaryReader ficheroBinario = new BinaryReader(fichero, Encoding.UTF8));
Console.WriteLine(Leido de datos.dat: {0}, ficheroBinario.ReadString());
Lo que es importante es tener en cuenta que la codificacin a usar al leer los caracteres
de un fichero de texto debera ser siempre la misma que la que se us para escribirlos en
l, pues si no podran obtenerse resultados extraos al interpretarse mal los datos ledos.
Ficheros de texto
Ya se ha visto que a travs de los objetos BinaryReader y BinaryWriter pueden leerse o
escribirse textos en ficheros mucho ms cmodamente que trabajando a nivel de bytes
con sus caracteres, como se hara usando directamente FileStreams. Sin embargo, la
BCL proporciona otra pareja de tipos llamados TextReader y TextWriter con los que
estas tareas se hacen incluso ms sencillas.
Lectura de ficheros de texto
Para facilitar la lectura de flujos de texto TextReader ofrece una familia de mtodos que
permiten leer sus caracteres de diferentes formas:
De uno en uno: El mtodo int Read() devuelve el prximo carcter del flujo como, o
un 1 si se ha llegado a su final. Tras cada lectura la posicin actual en el flujo se
Por grupos:
Por lneas:
Por completo: Un mtodo muy til que se ha incluido en la BCL y que las libreras
de muchos otros lenguajes y plataformas no tienen es string ReadToEnd(), que nos
devuelve una cadena con todo el texto que hubiese desde la posicin actual del flujo
sobre el que se aplica hasta el final del mismo (o null si ya estbamos en su final)
El mtodo int Read(out char[] caracteres, int inicio, int nCaracteres) lee
un grupo de nCaracteres y los almacena a partir de la posicin inicio en la tabla que se
le indica. El valor que devuelve es el nmero de caracteres que se hayan ledo, que
puede ser inferior a nCaracteres si el flujo tena menos caracteres de los indicados o
un 1 si se ha llegado al final del flujo. Tambin se incluye un mtodo ReadBlock()
que funciona de forma parecida al anterior pero que se queda a la espera de nuevos
caracteres si en el flujo no hay disponibles nCaracteres pero se espera que lleguen,
lo que puede ocurrir cuando se lee de flujos asociados a una conexiones de red.
El mtodo string ReadLine() devuelve la cadena de texto correspondiente
a la siguiente lnea del flujo o null si se ha llegado a su final. Por compatibilidad con
los sistemas operativos ms frecuentemente usados, considera que una lnea de texto
es cualquier secuencia de caracteres terminada en \n, \r \r\n, aunque la cadena
que devuelve no incluye dichos caracteres.
Como la mayora de estos mtodos modifican la posicin actual en el flujo pueden darse
problemas de concurrencia si varios hilos usan a la vez un mismo TextReader. Para
evitarlos se ofrece un mtodo esttico TextReader Synchronized(TextReader textreader)
que devuelve una versin del TextReader que se le pasa como parmetro en la que se
asegura la exclusin mutua en el acceso a aquellos miembros suyos que podran dar
problemas al usarlos en entornos concurrentes.
Aparte de estos mtodos, los TextReaders tambin incluyen un mtodo Close() mediante
el que pueden liberarse los recursos que hubiesen acaparado para poder leer de la fuente
que tuviesen asociada. Obviamente, tras llamar a este mtodo no se podr seguir usando
el TextReader para leer de dicha fuente, e intentar de hacerlo provocar IOExceptions.
En realidad TextReader es una clase abstracta que define caractersticas comunes a
objetos capaces de extraer texto de fuentes genricas. Nosotros para leer ficheros lo que
usaremos sern objetos de su subclase StreamReader especializada en la extraccin de
texto de flujos. Sus constructores bsicos son:
StreamReader(Stream flujo)
StreamReader(Stream flujo, Encoding codificacion)
StreamReader(Stream flujo, bool autodetectarCodificacin?)
StreamReader(Stream flujo,Encoding codificacion,bool autodetectarCodificacin)
StreamReader(Stream flujo,Encoding codificacion,bool autodetectarCodificacin,
int tamaoBuffer)
Como se ve, estos constructores permiten crear el StreamReader a partir del flujo cuyo
lectura facilitan (puede luego recuperarse leyendo su propiedad Stream BaseStream
{get;}) y que pueden configurarse para usar un cierta codificacin y tamao de buffer
La codificacin tomada por defecto es UTF-8, aunque puede cambiarse dndole el valor
apropiado al parmetro codificacin. Adems, tambin se incluye un parmetro llamado
autodetectarCodificacin que indica si se desea que se deduzca la codificacin del texto a
partir de los valores de sus primeros bytes. Si no pudiese autodetectarse se considerara
que es la indicada en codificacin UTF-8 en su defecto. En cualquier caso, siempre es
posible saber cul es la codificacin considerada a travs de la propiedad Encoding
CurrentEncoding {get;} del StreamReader, pero al leerla hay que tener en cuenta que su
valor puede cambiar tras la primera lectura como consecuencia de una autodeteccin
El tamao del buffer que internamente usarn los StreamReaders para alojar los
caracteres que lean es por defecto de 256 caracteres (4096 bytes) Sin embargo, dicho
tamao puede cambiarse al crearlos indicando su tamaoen nmero de caracteres- en
el parmetro tamaoBuffer de su constructor. Dicho valor ha de ser de menos 128 (2048
bytes), y si se le diese alguno menor sera ignorado y se tomara el mnimo sealado.
La existencia de un buffer implica que pueda ocurrir que haya momentos en los que la
posicin actual en el StreamReader sea distinta a la posicin actual en su Stream interno
debido a que se hayan ledo ms caracteres de los solicitados con la idea de guardar
temporalmente los sobrantes en el buffer por si se solicita en breve su lectura. Adems,
los StreamReaders suponen que a su flujo interno slo se acceder a travs de ellos, por
lo que si se le hiciesen cambios externamente podran no verlos directamente por estar
leyendo de su buffer interno. Para resolver los problemas de inconsistencia que esto
podra provocar, todo StreamReader cuenta con un mtodo DiscardBufferedData() que
vaca por completo dicho buffer.
Como un uso muy frecuente de los StreamReaders es extraer textos de ficheros, tambin
se han definido versiones de cada uno de los constructores anteriormente vistos que en
lugar de tomar como primer parmetro un Stream lo que toman es la cadena con la ruta
del fichero a leer y a partir de ella generan el Stream apropiado. stos son:
StreamReader(string rutaFichero)
StreamReader(string rutaFichero, Encoding codificacion)
StreamReader(string rutaFichero, bool autodetectarCodificacin?)
StreamReader(string rutaFichero, Encoding codificacion,
bool autodetectarCodificacin?)
StreamReader(string rutaFichero, Encoding codificacion,
bool autodetectarCodificacin?, int tamaoBuffer)
try
{
if (args.Length < 1)
{
Console.WriteLine("Error: Llamada incorrecta. Formato correcto de uso:");
Console.WriteLine("\n\t VisorTexto <rutaFichero>");
Environment.Exit(1);
}
String nombreFichero = args[0];
StreamReader contenido = new StreamReader(nombreFichero, true);
Console.WriteLine("Contenido de {0}:\n{1}", nombreFichero, contenido.ReadToEnd());
}
catch (FileNotFoundException)
{ Console.WriteLine("Error: Fichero {0} no encontrado", args[0]); }
}
}
Ntese cmo ahora la extraccin del contenido del fichero es mucho ms sencilla que en
el caso de trabajar directamente con el FileStream asociado al contenido del fichero,
habindose reducido el complejo mtodo muestraContenido() del visor hexadecimal de la
anterior aplicacin de ejemplo a una nica llamada a ReadToEnd() en sta.
Escritura en ficheros de texto
Del mismo modo que los TextReaders facilita la lectura de textos procedentes de fuentes
genricas, los TextWriters hace lo propio con la escritura en destinos genricos. Para ello
ofrece mtodos que permiten:
Escribir cadenas de texto: Todo TextWriters cuenta con mtodo Write() que permite
escribir cualquier cadena de texto en el destino que tengan asociado. Esta cadena se
les pasa como primer parmetro, pero no tiene porqu ser un string sino que puede
ser un objeto de cualquier tipo y lo que se hara sera aplicarle su mtodo ToString()
para obtener su representacin en forma de cadena de texto.
Tambin puede pasrsele como parmetro una tabla char[], en cuyo caso lo que se
escribira seran todos sus caracteres en el mismo orden en que apareciesen en ella.
Si adems se usa la sobrecarga Write(char[] caracteres, int posicin, int n) se podra
delimitar los caracteres de la tabla para slo escribir los n primeros incluidos a partir
de la posicin de la tabla indicada.
Como pasa con el Write() de Console, tambin existe sobrecarga de este mtodo que
toma como parmetros la cadena a mostrar y un nmero indefinido de objetos cuya
representacin como cadenas se escribir en el destino en el lugar correspondiente a
las subcadenas {i} de la cadena que se le pas como primer parmetro.
configurar cul queremos que se use al escribir en su destino asociado. Su valor por
defecto es el \r\n correspondiente al indicador de fin de lnea en Windows, pero
tambin puede drsele el valor \n correspondiente a Linux3.
Cuando se usen cualquiera de estos mtodos hay que tener cuidado con los objetos con
valor null que se les pasen, pues no escriben nada en su lugar en el destino del
TextWriter pero tampoco lanzan ningn tipo de excepcin que informe de ello.
Adems, tambin hay que tener cuidado al usarlos en entornos concurrente ya que si
varios hilos usan a la vez un mismo TextWriter podran presentarse problemas de
concurrencia. Para evitarlos, a partir del TextWriter puede generarse una versin suya
donde se asegure la exclusin mutua al acceder a sus miembros conflictivos llamando
para ello a su mtodo esttico TextWriter Synchronized(TextWriter textwriter)
Aparte de los mtodos anteriores, todo TextWriter tambin incorpora el tpico mtodo
Close() mediante el que pueden liberarse los recursos que hubiese acaparado para ser
capaz de escribir en su destino asociado. Adems, como para optimizar dichas escrituras
las realizan a travs de un buffer intermedio, tambin cuentan con el mtodo estndar
Flush() encargado de vaciarlo para cuando, sin necesidad de cerrar el TextWriter, se
necesite asegurar el volcado fsico de todo lo escrito a travs de l.
Como TextReader, la clase TextWriter es en realidad abstracta y nosotros para usar la
funcionalidad descrita lo que en realidad usaremos sern objetos de una subclase suya
especializada en la escritura en flujos denominada StreamWriter. Sus constructores
permiten configurar el flujo donde se escribir, y la codificacin y tamao de buffer a
usar de forma parecida a como se hace en los de StreamReader. Los bsicos son:
StreamReader(Stream flujo)
StreamReader(Stream flujo, Encoding codificacion)
StreamReader(Stream flujo, Encoding codificacion, int tamaoBuffer)
Como se ve, stos constructores toman un parmetro adicional llamado concatenar. Este
parmetro permite indicar qu ha de hacerse si el fichero donde se desea escribir ya
existe. Si vale false ser sobreescrito, pero si vale true se concatenarn a su final los
nuevos datos que se escriban en l. Por defecto vale true.
Aparte de los miembros de TextWriter ya explicados, los StreamWriters incorporan una
propiedad bool AutoFlush mediante la que es posible conseguir, si se le da el valor true,
que todo texto escrito en l usando sus mtodos Write() y WriteLine() sea inmediatamente
3
Aunque puede tomar cualquier otro valor no se recomienda drselo porque los TextReaders slo pueden
leer adecuadamente las fuentes de texto cuyos indicadores de fin de lnea sean \r o \r\n
volcado al dispositivo que tiene asociado sin que el programador tenga que acordarse de
llamar explcitamente a Flush() tras cada escritura.
Manipulacin del sistema de archivos
Ya hemos visto que mediante objetos FileInfo podemos realizar las tareas ms comunes
de manipulacin de archivos, acceso a informacin sobre ellos y a su contenido; y que
mediante objetos DirectoryInfo podemos hacer lo propio con directorios.
Sin embargo, la realizacin de estas tareas con ellos implica la creacin de objetos de
los tipos citados y ello puede llegar a ser un incordio si slo tenemos que crearlos para
hacer una operacin concreta, como por ejemplo renombrar un fichero. Para evitar esto,
la BCL incluye una serie de tipos que slo tiene mtodos estticos y que actan como
utilidades que permiten realizar ese tipo de tareas a partir de las rutas de los ficheros en
una sola instruccin, sin tener que andar creando objetos intermedios. El tipo ofrecido
para manipular as ficheros es File, mientras que para los directorios es Directory.
Hay que tener en cuenta que cada vez que se llame a uno de los mtodos de estos tipos
se harn las comprobaciones de seguridad necesarias para ver si se tienen los permisos
necesarios sobre los archivos que intervengan en la operacin. Sin embargo, cuando se
usan objetos FileInfo o DirectoryInfo las comprobaciones slo se hacen sobre el fichero o
directorio que ellos representen la primera que se solicite una operacin sobre ellos. Por
tanto, para a hacer mltiples operaciones sobre un mismo fichero o directorio es ms
eficiente usar objetos intermedios que los mtodos estticos de los tipos anteriores.
Aparte de versiones ms cmodas de utilizar de los servicios ofrecidos por los objetos
FileInfo y DirectoryInfo, estos tipos tambin proporcionan nuevos servicios relativos a
ficheros y directorios que permiten realizar tareas tan tiles como navegar por el sistema
de archivos de una mquina u obtener la lista de unidades lgicas de las que dispone.
Manipulacin de ficheros
Como ya se ha dicho, el tipo ofrecido en la BCL como utilidad mediante cuyos mtodos
estticos se facilita la realizacin de las operaciones con ficheros ms comunes es File.
Los servicios ofrecidos por los mtodos de este tipo pueden agruparse en:
Manipulacin de directorios
Como tambin ya se ha comentado, la clase Directory centraliza todo tipo de servicios
relativos a la manipulacin de directorios y permite utilizarlos de una manera rpida y
cmoda, sin necesidad de tener que andar creando objetos DirectoryInfo intermedios. En
general, estos servicios que ofrece podemos agruparlos en tres grandes categoras:
Acceso a informacin sobre directorios: Igual que, como se ha visto, File cuenta
con miembros que permiten acceder a la informacin que cada sistema operativo
almacenan sobre los ficheros de su sistema de archivos, Directory hace lo propio con
la relativa a los directorios. Para ello, ofrece los siguientes mtodos:
DateTime getCreationTime(string rutaDirectorio)
SetCreationTime(string rutaDirectorio, DateTime fechaCreacin)
DateTime getLastAccessTime(string rutaDirectorio)
SetCreationTime(string rutaDirectorio, DateTime FechaltimoAcceso)
DateTime getLastWriteTime(string rutaDirectorio)
SetWriteTime(string rutaDirectorio, DateTime FechaltimaModificacin)
Sin embargo, como en ocasiones puede que slo nos interese obtener los contenidos
del directorio cuyos nombres sigan un cierto patrn, de los mtodos anteriores
tambin se ofrece una sobrecarga que admite como parmetro una cadena que
indique el patrn que han de seguir los nombres a obtener. Este patrn puede usar el
carcter ? para indicar que en su lugar se admite cualquier carcter y el carcter *
para indicar que en su lugar se admite cualquier nmero (incluido 0) de cualesquiera
caracteres. As, los nombres de los ficheros almacenados en C:\Pruebas que
empiecen por A y tengan extensin txt pueden obtenerse con:
String[] contenidoPruebas = File.GetFiles(@C:\Pruebas, A*.txt);
Navegacin por el sistema de archivos: A veces puede interesar escribir las rutas
de los ficheros y/o directorios a los que se quiera acceder de manera relativa a un
determinado directorio, para lo que es necesario ir cambiando de directorio actual.
Esto puede ser til porque permite que las aplicaciones accedan a ficheros instalados
con ellas sin tener porqu conocer la ruta completa donde se instalaron: se accedera
a los ficheros con rutas relativas a la ubicacin de su ejecutable.
Una segunda utilidad de escribir las rutas relativamente es que ello puede
simplificar mucho las rutas cuando se va a acceder a varios ficheros y/o directorios
ubicados dentro de uno determinado. Por ejemplo, para acceder a varios ficheros
ubicados en d:\pruebas\versin1 lo mejor puede ser establecer ese directorio
como el actual y as podremos referenciarlos sin tener que escribir el nombre de su
directorio padre.
Como se ve, para poder escribir las rutas de los ficheros de manera relativa a otros
directorios puede necesitarse poder cambiar el directorio considerado como actual.
Para ello, Directory proporciona el mtodo esttico SetCurrentDirectory(string
rutaNuevoDirectorioActual), y tambin incluye un string GetCurrentDirectory() que
permite consultar cul es el en cada instante el directorio considerado como actual.
Para ayudarnos en nuestros movimientos a travs de los rboles de directorios se
ofrece tambin un mtodo DirectoryInfo GetParent(string rutaDirectorio), que
devuelve el objeto DirectoryInfo que representa al padre del directorio indicado.
Hay sistemas operativos que trabajan con mltiples unidades de disco lgicas, que
se corresponden con los distintos dispositivos de almacenamiento de la mquina
sobre la que corren o con porciones lgicas de ellos y que tendrn su propio rbol de
directorios. Para facilitar la navegacin en estos casos, en Directory se ha incluido
un mtodo string GetDirectoryRoot(string rutaDirectorio) que devuelve el nombre del
directorio raz del rbol asociado a la unidad de disco a la que pertenece al directorio
indicado en el formato <nombreUnidad>:\; y un mtodo string[] GetLogicalDrives()
que devuelve una tabla con los nombres, en el formato anterior, de todas y cada una
de las unidades de disco lgicas de la mquina sobre la que se ejecuta.
Modificacin de los directorios del sistema de archivos: Para realizar las tpicas
operaciones de creacin, borrado y movimiento de directorios la clase Directory
incluye los mtodos estticos DirectoryInfo CreateDirectory(string rutaDirectorio),
Delete(string rutaDirectorio) y Move(string rutaFuente, string rutaDestino)
Tenga en cuenta que las operaciones que realice sobre un directorio afectan a todos
los ficheros y subdirectorios contenidos en el mismo. Por ello, si borra un directorio
ste ha de estar vaco o si no Delete() producir una IOException para evitar borrados
accidentales, aunque de todas formas puede pasarle true como segundo parmetro
para indicarle explcitamente su deseo de borrar el contenido del directorio.
Tenga tambin en cuenta que operaciones como CreateDirectory() o Move() lanzarn
IOExceptions si los directorios a crear (en el caso de Move() sera el de rutaDestino)
ya existen; y que Delete() producir una DirectoryNotFoundException si el directorio
a borrar no existe. Para comprobar cmodamente la existencia de directorios y evitar
as problemas puede usar el mtodo bool Exists(string rutaDirectorio) de Directory.
Manipulacin de rutas
Deteccin de cambios en el sistema de archivos
En ambos casos tenga en cuenta que el observador vigilar el contenido del directorio
que se le ha especificado aunque posteriormente dicho directorio sea renombrado. Ello
lo consigue gracias a que lo que para l lo identifica no es su nombre sino el manejador
que le asocia el sistema operativo, y ste no cambia por mucho que sea renombrado.
Los observadores creados como se ha dicho detectarn los cambios que se produzcan en
cualquiera de los ficheros y subdirectorios del directorio que se les indique, pero si se
desea que slo se centre en algunos de ellos puede configurarse su propiedad string
Filter para ello. A esta propiedad se le pasara una cadena en la que se indicara el patrn
que seguirn los ficheros a vigilar, representando en ella cualquier carcter mediante un
? y cualquier combinacin de cualesquiera caracteres con un *. Por ejemplo, para slo
vigilar los ficheros de extensin txt de c:\datos podra hacerse:
FileSystemWatcher observador = new FileSystemWatcher(@c:\datos);
observador.Path=*.txt;
En cualquier caso, hay que sealar que, por eficiencia, cuando se crea un observador
este slo vigila por defecto el contenido del directorio que se les indique pero no el de
los subdirectorios del mismo. Si queremos que ello tambin ocurra hay que configurar
explcitamente su propiedad bool IncludeSubdirectories. Por tanto, todo el sistema de
archivos de la unidad C:\ de una mquina podra vigilarse as:
FileSystemWatcher observador = new FileSystemWatcher(@c:\);
observador.IncludeSubdirectories;
Ntese que con IncludeSubdirectories indica que desea vigilar no slo el contenido del
directorio indicado y sus subdirectorios sino tambin el de los subdirectorios de stos, y
el de los subdirectorios de los subdirectorios de estos, etc.
En el lado opuesto, tambin es posible configurar un FileSystemWatcher para que slo
vigile los cambios que se produzcan en un fichero concreto. Para ello basta asignar a su
propiedad Path la ruta del directorio al que pertenece y a Filter el nombre de ese fichero.
Por ejemplo, para vigilar el fichero c:\datos\dato1.dat puede hacerse:
FileSystemWatcher observador = new FileSystemWatcher(@c:\datos, dato1.dat);
Attributes
CreationTime
DirectoryName
FileName
LastAccess
LastWrite
Size
Security
Atributos de ficheros
Fecha de creacin de ficheros
Nombres de directorios
Nombres de ficheros
Fecha de ltimo acceso de ficheros
Fecha de ltima modificacin de ficheros
Tamao de ficheros
Permisos de seguridad de ficheros
Estos literales puede combinarse con operaciones lgicas OR. Por ejemplo, los cambios
de nombre o atributos producidos en los ficheros de c:\datos pueden detectarse con:
FileSystemWatcher observador = new FileSystemWatcher(@c:\datos);
observador.NotifiyFilter = NotifyFilters.DirectoryFile | NotifyFilters.Attributes;
que dejara el cdigo bloqueado en espera de que se produjese algn cambio del tipo
indicado en tipoCambio. Este parmetro es de una enumeracin cuyos los literales son:
Literal
Changed
Renamed
Created
Deleted
All
Ntese que el problema de utilizar un mecanismo como este para detectar los cambios
es que deja bloqueado al hilo que llama a WaitForChanged() hasta que se produzca el
cambio, lo que puede reducir mucho el rendimiento de la aplicacin si ste tarda en
producirse (o incluso bloquearla si nunca llega a producirse)
Para evitar esto y dar la posibilidad de que se puedan realizar otras tareas mientras llega
el evento puede especificarse un como segundo parmetro de tipo int al llamar a
WaitForChanged() que para indicar el tiempo mximo, en ms., que se de esperarse a que
ocurra el cambio, y si no ocurre pasado ese tiempo finalizar la ejecucin del mtodo y
se marcar a true la propiedad bool TimedOut del WaitForChangedResult para indicarlo.
Deteccin asncrona de cambios
La opcin anterior de acotar el tiempo mximo de espera de WaitForChanged() para
poder realizar otras tareas mientras llegue el cambios esperado permite muchas veces
notables mejoras de eficiencia. Sin embargo, una solucin asncrona donde se indique al
FileSystemWatcher qu ha hacerse ante cada tipo de cambio y se le deje funcionando en
un hilo aparte que se encargar de detectarlos y realizar las acciones apropiadas puede
ser mejor, pues en mquinas con varios procesadores es ms eficiente y adems da lugar
a cdigos ms claros donde no se intercalan continuas llamadas a WaitForChanged() con
sus consiguientes esperas acotadas y comprobaciones de TimedOut asociadas.
Para asociar cdigos a ejecutar ante cada tipo de cambio detectado FileSystemWatcher
proporciona los siguientes eventos:
Evento
Changed
Renamed
Created
Deleted
Esto se debe a que aparte de la informacin proporcionada por el tipo delegado anterior,
ante un renombrado puede convenir disponer tambin de informacin sobre el fichero o
directorio afectado antes de ser renombrardo. Eso es precisamente lo que se aporta el
donde el mtodo muestraMensaje() estara definido como sigue en alguna parte dentro
de la misma definicin de tipo a la que perteneciese el cdigo anterior:
void muestraMensaje(object emisor, FileSystemEventArgs args)
{
Console.WriteLine(Creado fichero {0}, args.FullPath);
}
Hay que tener en cuenta que una vez asociado los cdigos de respuesta a los diferentes
eventos del FileSystemWatcher que queramos controlar hay que poner true su propiedad
bool EnableRaisingEvents para lazar el hilo que har las detecciones de cambios y
llamadas a los cdigos asociados a los eventos. Adems, en cualquier momento es
posible parar dicho hilo sin ms que volver a poner a false esta propiedad.
ha sido definido como un componente4, por lo que tambin podr
crear objetos suyos arrastrndolos desde la caja de herramientas de Visual Studio.NET
hasta su ventana de diseo. Sin embargo, si lo hace tenga cuidado ya que entonces el
valor que se le dar por defecto a su propiedad EnableRaisingEvents es true.
FileSystemWatcher
Como puede observar el cdigo del programa es muy sencillo, simplemente comprueba
que se le haya pasado como argumento la ruta de algn directorio; y si es as crea un
observador que vigila dicho directorio y que cada vez que se crea algn fichero sobre el
mismo lo cifra incrementado en tres unidades el valor cada uno de sus bytes (el clsico
criptsosistema de Csar)
Problemas de desbordamientos del buffer de cambios
Cada FileSystemWatcher cuenta con un buffer interno donde encola temporalmente
informacin sobre cambios producidos en el directorio que vigila mientras estaba
procesando, para as procesarlos en orden en cuanto termine con el que estaba. Ahora
bien, si se encolasen tantos que se saturarse ese buffer y se perdiese la deteccin de los
que no cupiesen en l, el observador lanzara un evento ErrorEventHandler Error al que
para tratarlo podramos asociarle un mtodo con la signatura que sigue:
delegate void ErrorEventHandler(object emisor, ErrorEventArgs args );
Una forma de evitar errores en estos casos puede ser cambiar el tamao de este buffer
interno, que por defecto es de 8192 bytes. Para ello puede usarse la propiedad int
InternalBufferSize del FileSystemWatcher, a la que se le ha de dar un valor superior a
4096 y recomendablemente (al menos en mquinas Intel) mltiplo de dicha cantidad.
Otra forma para evitarlos es modificar la propiedad NotifyFilter para restringir el tipo de
cambios a detectar y por tanto reducir as su nmero. Lo que no se le ocurra nunca es
intentar reducirlos usando Filter para filtrar slo ciertos archivos de la ruta indicada en
Path, pues el buffer en realidad almacena todos los cambios producidos en dicha ruta,
incluidos los que ocurran sobre los ficheros que no entren en el filtro (slo que cuando
toque procesar estos sern descartados en lugar de procesados)
Ficheros temporales
Muchos sistemas operativos suelen incluir un directorio que ofrecen a las aplicaciones
como almacn temporal de ficheros que slo son tiles mientras la aplicacin se est
ejecutando pero que una vez ejecutada carecen de sentido y puede eliminarse.
Como segn el tipo de sistema operativo que tenga instalado el usuario o segn cmo lo
tenga configurado la ruta de ste puede variar, la BCL incluye un mtodo string
GetTempPath() mediante el que puede obtenerse esta ruta para cada mquina concreta.
Por otra parte, como este directorio es compartido entre mltiples aplicaciones, no es
muy recomendable que creemos en l ficheros con nombres elegidos por nosotros
mismos ya que podran entrar en conflicto con los ya instalados por otras aplicaciones.
En su lugar, es mejor usar el mtodo string GetTempFileName () de Path, que devuelve
un nombre de fichero temporal que seguro que est libre. Por ejemplo, dado el cdigo:
Console.WriteLine(Path.GetTempFileName());