CodeGym /Cursos /C# SELF /Clase FileStream

Clase FileStream

C# SELF
Nivel 35 , Lección 3
Disponible

1. Clase FileStream: trabajar por trozos

Puedes imaginar FileStream como una tubería de agua conectada directamente a tu archivo en disco. Por esa tubería puedes dejar pasar datos de forma controlada: enviar bytes al archivo (escritura) o recibir bytes del archivo (lectura). A diferencia de métodos de más alto nivel, que simplemente "te dan un vaso de agua", FileStream te da acceso directo al "grifo", permitiéndote regular el flujo de agua (bytes) según lo necesites.

FileStream trabaja a nivel de bytes. Eso significa que le da igual si es texto o una imagen; para él todo es simplemente una secuencia de bytes. Si trabajas con archivos de texto usando FileStream, tendrás que convertir las cadenas a bytes tú mismo (usando una codificación, por ejemplo, UTF-8) al escribir, y al revés al leer.

¿Cuándo exactamente necesitas FileStream?

  • Trabajar con archivos muy grandes: Cuando el archivo es tan enorme que no puedes (o no tiene sentido) cargarlo entero en la RAM (por ejemplo, logs de gigas, archivos de vídeo). FileStream te permite leer o escribir datos por partes (chunks), usando la memoria de forma más eficiente.
  • Datos binarios: Si trabajas con archivos que no son texto normal (imágenes, audio, vídeo, objetos serializados, archivos de bases de datos), FileStream es la herramienta principal, porque te da acceso directo a los bytes.
  • Control detallado sobre los modos de acceso: ¿Necesitas abrir un archivo para leer *y* escribir a la vez? ¿O abrirlo para que otros programas no puedan acceder? ¿O al revés, que varios procesos puedan leer el archivo al mismo tiempo? FileStream te da control total sobre estos modos.
  • Operaciones asíncronas: En apps modernas de alto rendimiento (por ejemplo, servidores web) es clave que las operaciones de archivos no bloqueen el hilo principal. FileStream soporta métodos asíncronos (ReadAsync, WriteAsync), así tu programa sigue siendo ágil.
  • Lectura/escritura parcial o acceso aleatorio: Si necesitas leer datos de un sitio concreto del archivo (por ejemplo, desde el byte 500) o escribir en medio del archivo, FileStream te deja mover el puntero de archivo como quieras.

Error típico de novato: Usar FileStream para tareas súper simples, como escribir una sola línea de texto en un archivo pequeño. En esos casos es demasiado. FileStream es para escenarios específicos y más complejos.

2. Crear y abrir un FileStream

Para empezar a trabajar con un archivo usando FileStream, tienes que crear una instancia. Se hace con el constructor, que recibe varios parámetros importantes que definen cómo quieres interactuar con el archivo.

using System;
using System.IO;   // Obligatorio para trabajar con FileStream
using System.Text; // Para trabajar con codificaciones (convertir strings a bytes y viceversa)

Ejemplo de lectura básica de un archivo de texto usando FileStream:

Primero creamos un archivo para tener algo que leer.

// Creamos un archivo de texto simple para la demo
string path = "example_filestream.txt";

// Abrimos el stream para leer.
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

// Creamos un buffer (array de bytes) para guardar temporalmente los datos leídos.
byte[] buffer = new byte[fs.Length];

// Leemos los bytes del stream al buffer.
int bytesRead = fs.Read(buffer, 0, buffer.Length);

// Convertimos los bytes leídos de vuelta a string usando la codificación adecuada (por ejemplo, UTF-8).
string content = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("Leído del archivo con FileStream: " + content);

fs.Close(); //cerramos el stream

Parámetros principales

  • FileMode: cómo abrir el archivo (Open, Create, Append OpenOrCreate etc.)
  • FileAccess: qué hacer con el archivo (Read, Write, ReadWrite)
  • FileShare: si otros procesos pueden usar el archivo al mismo tiempo (normalmente para cosas simples no hace falta)

Error típico: Si olvidas cerrar el FileStream, el archivo puede quedarse "bloqueado" — ¡no podrás borrarlo ni abrirlo de nuevo!

3. Parámetros del constructor de FileStream

El constructor de FileStream te deja ajustar muy bien cómo quieres abrir el archivo. Estos son sus parámetros principales:

new FileStream(
    string path,           // Ruta al archivo (absoluta o relativa)
    FileMode mode,         // Cómo abrir el archivo (crear, abrir, sobrescribir, etc.)
    FileAccess access,     // Qué acceso se permite (solo lectura, solo escritura, lectura+escritura)
    FileShare share        // Cómo pueden otros procesos acceder al archivo mientras está abierto (opcional)
);

Vamos a ver los valores de los enums FileMode y FileAccess:

FileMode (Modo de apertura de archivo):

Define cómo el sistema operativo debe tratar el archivo al abrirlo.

  • FileMode.Open: Abre un archivo existente. Si el archivo no existe en la ruta indicada, lanza FileNotFoundException.
  • FileMode.Create: Crea un archivo nuevo. Si ya existe un archivo con ese nombre, se sobrescribe completamente (se borra su contenido).

FileAccess (Permisos de acceso al archivo):

Define qué operaciones puede hacer tu programa con el archivo abierto.

  • FileAccess.Read: El archivo se abre solo para lectura. No podrás escribir en él.
  • FileAccess.Write: El archivo se abre solo para escritura. No podrás leer de él.
  • FileAccess.ReadWrite: El archivo se abre para lectura y escritura. Es el modo más flexible, pero también más delicado porque tienes que gestionar la posición en el stream.

FileShare (Acceso compartido):

Define cómo otros procesos pueden abrir el mismo archivo mientras tu programa lo tiene abierto. Es clave para evitar bloqueos.

  • FileShare.Read: Otros procesos pueden leer el archivo mientras tu FileStream lo tiene abierto, pero no pueden escribir.
  • FileShare.Write: Otros procesos pueden escribir en el archivo mientras tu FileStream lo tiene abierto, pero no pueden leer.
  • FileShare.ReadWrite: Otros procesos pueden leer y escribir en el archivo. Es el modo más abierto, pero puede causar conflictos si varias apps modifican el archivo a la vez.

Más sobre los modos de trabajo en la próxima lección.

4. Leer y escribir datos con FileStream

Cuando usas FileStream, manipulas bytes. Eso significa que para datos de texto tienes que convertir entre strings y arrays de bytes usando clases de codificación (por ejemplo, System.Text.Encoding.UTF8).

Escribir datos en un archivo con FileStream

Al escribir, conviertes tus datos (por ejemplo, una string) en un array de bytes y luego escribes esos bytes en el stream.

string outputPath = "user_data.txt";
string userName = "Iván Petrov";

// Convertimos la string a array de bytes usando codificación UTF-8
byte[] userNameBytes = Encoding.UTF8.GetBytes(userName);

// Abrimos FileStream para escribir.
FileStream fsWrite = new FileStream(outputPath, FileMode.Create, FileAccess.Write);

// Escribimos el array de bytes en el stream.
fsWrite.Write(userNameBytes, 0, userNameBytes.Length);
Console.WriteLine($"Nombre '{userName}' guardado correctamente en el archivo '{outputPath}'.");

fsWrite.Close(); //cerramos el stream

Leer datos de un archivo con FileStream

Al leer, sacas los bytes del stream a un buffer y luego conviertes esos bytes al formato que quieras (por ejemplo, string).

string inputPath = "user_data.txt";

// Abrimos FileStream para leer
FileStream fsRead = new FileStream(inputPath, FileMode.Open, FileAccess.Read);

// Creamos un buffer del tamaño del archivo para leer todos los bytes.
byte[] buffer = new byte[fsRead.Length];

// Leemos los bytes del stream al buffer. 
int bytesRead = fsRead.Read(buffer, 0, buffer.Length);

// Convertimos los bytes leídos a string.
string loadedUserName = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"Nombre del archivo (con FileStream): {loadedUserName}");

fsRead.Close(); //cerramos el stream

5. Trabajar con archivos grandes

La principal ventaja de FileStream frente a los métodos File.ReadAll... es que puedes leer y escribir el archivo por partes, lo cual es clave para archivos grandes que no caben enteros en la RAM.

Leer archivos grandes por partes

Cuando lees un archivo por partes, usas un buffer (array de bytes) de tamaño fijo y vas leyendo datos en él hasta llegar al final del archivo.

string bigFilePath = "bigfile.bin";     //archivo grande 
int bufferSize = 4096;                  // Tamaño del buffer, por ejemplo, 4 KB
byte[] buffer = new byte[bufferSize];   // Creamos el buffer
int bytesRead;                          // Cantidad de bytes realmente leídos
long totalBytesRead = 0;                // Total de bytes leídos

// abrimos el stream 
FileStream fs = new FileStream(bigFilePath, FileMode.Open, FileAccess.Read);
int chunkNumber = 1;

// El bucle sigue mientras fs.Read() devuelva un número positivo de bytes
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
    totalBytesRead += bytesRead;
    Console.WriteLine($"Parte {chunkNumber++}: leídos {bytesRead} bytes. Total: {totalBytesRead} bytes.");

    // !!! Aquí procesas los "trozos" de datos leídos !!!
   
}

fs.Close(); //cerramos el stream

Nota importante: El método fs.Read(buffer, offset, count) no garantiza que llene todo el buffer. Devuelve la cantidad real de bytes leídos, que puede ser menor que count (sobre todo al final del archivo). Usa siempre el valor devuelto bytesRead para procesar bien los datos.

Escribir grandes cantidades de datos por partes

Igual que al leer, puedes escribir grandes volúmenes de datos en un archivo por partes.

string bigOutputPath = "big_output.txt";
string longText = new string('A', 1_000_000); // Cadena de un millón de 'A'
byte[] longTextBytes = Encoding.UTF8.GetBytes(longText);
int writeBufferSize = 1024; // Escribimos de 1 KB en 1 KB

// FileMode.Create: creamos el archivo
FileStream fsWrite = new FileStream(bigOutputPath, FileMode.Create, FileAccess.Write);

for (int i = 0; i < longTextBytes.Length; i += writeBufferSize)
{
    // Calculamos cuántos bytes quedan por escribir en esta iteración
    int bytesToWrite = Math.Min(writeBufferSize, longTextBytes.Length - i);

    // Escribimos la porción de datos de longTextBytes, empezando en el offset 'i'
    fsWrite.Write(longTextBytes, i, bytesToWrite);
    Console.WriteLine($"Escritos {bytesToWrite} bytes");
}

fsWrite.Close(); //cerramos el stream

6. Trampas poco obvias y errores típicos

Incluso con una herramienta tan potente como FileStream, hay detalles que tienes que tener en cuenta:

Buffering y Flush(): Como ya se dijo, los datos pueden quedarse en el buffer en memoria y no llegar al disco. Si no usas using ni llamas a Close()/Dispose(), y simplemente cierras el programa, puedes perder datos. Llama siempre a Flush() (o usa using/Close()) para asegurarte de que los datos se escriben.

Gestión de excepciones: Los errores de entrada/salida (por ejemplo, IOException al intentar escribir en un disco lleno, UnauthorizedAccessException por falta de permisos, FileNotFoundException al intentar abrir un archivo que no existe en modo Open) son lo más normal. Envuelve siempre las operaciones con FileStream en bloques try-catch.

Codificaciones: Al trabajar con datos de texto usando FileStream (que va a bytes), tú eres el responsable de elegir la codificación correcta (Encoding.UTF8, Encoding.ASCII, Encoding.Unicode, etc.) al convertir strings a bytes y viceversa. Si eliges mal la codificación, tendrás "caracteres raros".

Gestión de la posición: Si usas Seek(), ten cuidado con los parámetros offset y origin, para no irte fuera del archivo o a un sitio inesperado.

Bloqueos de archivos (FileShare): Si abres un archivo con FileShare.None, otros procesos no podrán acceder a él. Puede ser un problema si otro programa (o incluso otro hilo en tu programa) intenta usar el mismo archivo. Elige siempre el modo FileShare más adecuado.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION