1. Classe FileStream: trabalhando em pedaços
O FileStream é tipo um cano de água ligado direto no seu arquivo no disco. Por esse cano, você pode controlar o fluxo de dados: mandar bytes pro arquivo (escrita) ou pegar bytes do arquivo (leitura). Diferente dos métodos mais de boa, que só "te dão um copo d'água", o FileStream te dá acesso direto à "torneira", deixando você regular o fluxo de água (bytes) do jeito que quiser.
O FileStream trabalha no nível de bytes. Ou seja, ele não tá nem aí se é texto ou imagem; pra ele tudo é só uma sequência de bytes. Se você for mexer com arquivos de texto usando FileStream, vai ter que converter as strings pra bytes (usando uma codificação, tipo UTF-8) na hora de gravar e fazer o contrário na hora de ler.
Quando que você realmente precisa do FileStream?
- Trabalhar com arquivos muito grandes: Quando o arquivo é tão gigante que não rola (ou não faz sentido) carregar tudo na memória RAM (tipo logs de gigabytes, vídeos). O FileStream deixa você ler ou gravar os dados em partes (chunks), usando a memória de forma mais esperta.
- Dados binários: Se você tá lidando com arquivos que não são texto puro (imagens, áudio, vídeo, objetos serializados, arquivos de banco de dados), o FileStream é a ferramenta principal, porque ele te dá acesso direto aos bytes.
- Controle detalhado dos modos de acesso: Precisa abrir o arquivo pra leitura *e* escrita ao mesmo tempo? Ou abrir de um jeito que nenhum outro programa consiga acessar? Ou, ao contrário, deixar vários processos lendo ao mesmo tempo? O FileStream te dá controle total sobre esses modos.
- Operações assíncronas: Em apps modernos de alta performance (tipo servidores web), é crucial que as operações de arquivo não travem a thread principal. O FileStream suporta métodos assíncronos (ReadAsync, WriteAsync), deixando o programa responsivo.
- Leitura/gravação parcial ou acesso aleatório: Se você precisa ler dados de um ponto específico do arquivo (tipo do 500º byte) ou gravar no meio do arquivo, o FileStream deixa você controlar a posição do ponteiro do arquivo.
Erro clássico de iniciante: Usar FileStream pra tarefas simples, tipo gravar uma linha de texto num arquivo pequeno. Nesses casos, ele é exagero. O FileStream é pra cenários mais específicos e complexos.
2. Criando e abrindo um FileStream
Pra começar a mexer num arquivo com FileStream, você precisa criar uma instância dele. Isso é feito com o construtor, que recebe alguns parâmetros importantes, dizendo como você quer interagir com o arquivo.
using System;
using System.IO; // Necessário pra usar FileStream
using System.Text; // Pra trabalhar com codificações (converter strings pra bytes e vice-versa)
Exemplo básico de leitura de arquivo texto usando FileStream:
Primeiro vamos criar um arquivo pra ter o que ler.
// Vamos criar um arquivo de texto simples pra demonstração
string path = "example_filestream.txt";
// Abrindo o stream pra leitura.
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
// Criando um buffer (array de bytes) pra guardar temporariamente os dados lidos.
byte[] buffer = new byte[fs.Length];
// Lendo os bytes do stream pro buffer.
int bytesRead = fs.Read(buffer, 0, buffer.Length);
// Convertendo os bytes lidos de volta pra string, usando a codificação certa (tipo UTF-8).
string content = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("Lido do arquivo via FileStream: " + content);
fs.Close(); //fechando o stream
Parâmetros principais
- FileMode: como abrir o arquivo (
Open,Create,AppendOpenOrCreatee outros) - FileAccess: o que fazer com o arquivo (
Read,Write,ReadWrite) - FileShare: se outros processos podem usar esse arquivo ao mesmo tempo (geralmente pra tarefas simples nem precisa)
Erro comum: Se você esquecer de fechar o FileStream, o arquivo pode ficar "travado" — aí não dá pra deletar ou abrir de novo!
3. Parâmetros do construtor do FileStream
O construtor do FileStream deixa você configurar direitinho como quer abrir o arquivo. Olha os principais parâmetros:
new FileStream(
string path, // Caminho do arquivo (absoluto ou relativo)
FileMode mode, // Como abrir o arquivo (criar, abrir, sobrescrever, etc)
FileAccess access, // Qual acesso permitido (só leitura, só escrita, leitura+escrita)
FileShare share // Como outros processos podem acessar o arquivo enquanto ele tá aberto (opcional)
);
Bora ver os valores dos enums FileMode e FileAccess:
FileMode (Modo de abertura do arquivo):
Diz como o sistema operacional deve lidar com o arquivo ao abrir.
FileMode.Open: Abre um arquivo já existente. Se não achar o arquivo no caminho, lança FileNotFoundException.FileMode.Create: Cria um novo arquivo. Se já existir um arquivo com esse nome, ele vai ser totalmente sobrescrito (o conteúdo antigo some).
FileAccess (Permissões de acesso ao arquivo):
Diz quais operações seu programa pode fazer com o arquivo aberto.
FileAccess.Read: Arquivo aberto só pra leitura. Não dá pra gravar nada nele.FileAccess.Write: Arquivo aberto só pra escrita. Não dá pra ler nada dele.FileAccess.ReadWrite: Arquivo aberto pra leitura e escrita. É o modo mais flexível, mas também mais chatinho, porque você tem que controlar a posição no stream.
FileShare (Compartilhamento):
Diz como outros processos podem abrir o mesmo arquivo enquanto ele tá aberto pelo seu programa. Muito importante pra evitar travamentos.
FileShare.Read: Outros processos podem ler o arquivo enquanto ele tá aberto pelo seu FileStream, mas não podem gravar.FileShare.Write: Outros processos podem gravar no arquivo enquanto ele tá aberto pelo seu FileStream, mas não podem ler.FileShare.ReadWrite: Outros processos podem ler e gravar no arquivo. É o modo mais liberal, mas pode dar ruim se vários programas mexerem no arquivo ao mesmo tempo.
Mais detalhes sobre os modos de trabalho na próxima aula.
4. Lendo e gravando dados com FileStream
Quando você usa o FileStream, tá mexendo com bytes. Então, pra dados de texto, tem que converter entre strings e arrays de bytes, usando as classes de codificação (tipo System.Text.Encoding.UTF8).
Gravando dados em arquivo com FileStream
Na hora de gravar, você converte seus dados (tipo uma string) pra um array de bytes, e aí grava esses bytes no stream.
string outputPath = "user_data.txt";
string userName = "Ivan Petrov";
// Convertendo a string pra array de bytes, usando UTF-8
byte[] userNameBytes = Encoding.UTF8.GetBytes(userName);
// Abrindo FileStream pra escrita.
FileStream fsWrite = new FileStream(outputPath, FileMode.Create, FileAccess.Write);
// Gravando o array de bytes no stream.
fsWrite.Write(userNameBytes, 0, userNameBytes.Length);
Console.WriteLine($"Nome '{userName}' gravado com sucesso no arquivo '{outputPath}'.");
fsWrite.Close(); //fechando o stream
Lendo dados de arquivo com FileStream
Na leitura, você pega os bytes do stream pro buffer, e depois converte esses bytes pro formato que quiser (tipo string).
string inputPath = "user_data.txt";
// Abrindo FileStream pra leitura
FileStream fsRead = new FileStream(inputPath, FileMode.Open, FileAccess.Read);
// Criando um buffer do tamanho do arquivo pra ler todos os bytes.
byte[] buffer = new byte[fsRead.Length];
// Lendo os bytes do stream pro buffer.
int bytesRead = fsRead.Read(buffer, 0, buffer.Length);
// Convertendo os bytes lidos pra string.
string loadedUserName = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"Nome do arquivo (via FileStream): {loadedUserName}");
fsRead.Close(); //fechando o stream
5. Trabalhando com arquivos grandes
A maior vantagem do FileStream sobre os métodos File.ReadAll... é poder ler e gravar o arquivo em partes, o que é essencial pra arquivos grandes que não cabem inteiros na RAM.
Lendo arquivos grandes em partes
Quando você lê o arquivo em partes, usa um buffer (array de bytes) de tamanho fixo e vai lendo nele até chegar no fim do arquivo.
string bigFilePath = "bigfile.bin"; //arquivo grande
int bufferSize = 4096; // Tamanho do buffer, tipo 4 KB
byte[] buffer = new byte[bufferSize]; // Criando o buffer
int bytesRead; // Quantidade de bytes realmente lidos
long totalBytesRead = 0; // Total de bytes lidos
// abrindo o stream
FileStream fs = new FileStream(bigFilePath, FileMode.Open, FileAccess.Read);
int chunkNumber = 1;
// O loop continua enquanto fs.Read() retorna um número positivo de bytes
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
totalBytesRead += bytesRead;
Console.WriteLine($"Parte {chunkNumber++}: lido {bytesRead} bytes. Total: {totalBytesRead} bytes.");
// !!! Aqui você processa os "pedaços" de dados lidos !!!
}
fs.Close(); //fechando o stream
Dica importante: O método fs.Read(buffer, offset, count) não garante que vai preencher todo o buffer. Ele retorna a quantidade de bytes realmente lidos, que pode ser menor que count (principalmente no final do arquivo). Sempre use o valor retornado em bytesRead pra processar os dados direito.
Gravando grandes volumes de dados em partes
Assim como na leitura, você pode gravar grandes volumes de dados no arquivo em partes.
string bigOutputPath = "big_output.txt";
string longText = new string('A', 1_000_000); // String com um milhão de 'A'
byte[] longTextBytes = Encoding.UTF8.GetBytes(longText);
int writeBufferSize = 1024; // Gravando 1 KB por vez
// FileMode.Create: criando o arquivo
FileStream fsWrite = new FileStream(bigOutputPath, FileMode.Create, FileAccess.Write);
for (int i = 0; i < longTextBytes.Length; i += writeBufferSize)
{
// Calculando quantos bytes faltam gravar nessa rodada
int bytesToWrite = Math.Min(writeBufferSize, longTextBytes.Length - i);
// Gravando o pedaço de dados de longTextBytes, começando do offset 'i'
fsWrite.Write(longTextBytes, i, bytesToWrite);
Console.WriteLine($"Gravado {bytesToWrite} bytes");
}
fsWrite.Close(); //fechando o stream
6. Pegadinhas e erros comuns
Mesmo com uma ferramenta poderosa como o FileStream, tem uns detalhes que você precisa ficar ligado:
Bufferização e Flush(): Como já falei, os dados podem ficar no buffer na memória e não ir pro disco de verdade. Se você não usar using nem chamar Close()/Dispose(), e só encerrar o programa, pode perder dados. Sempre chama Flush() (ou confia no using/Close()) pra garantir que os dados foram gravados.
Tratamento de exceções: Erros de I/O (tipo IOException quando tenta gravar num disco cheio, UnauthorizedAccessException por falta de permissão, FileNotFoundException ao tentar abrir um arquivo que não existe no modo Open) — são comuns. Sempre coloca as operações com FileStream dentro de blocos try-catch.
Codificações: Quando mexe com texto usando FileStream (que trabalha com bytes), você é totalmente responsável por escolher a codificação certa (Encoding.UTF8, Encoding.ASCII, Encoding.Unicode etc.) na hora de converter strings pra bytes e vice-versa. Se escolher errado, vai sair tudo "zuado".
Controle de posição: Se usar Seek(), presta atenção nos parâmetros offset e origin, pra não ir parar fora do arquivo ou num lugar inesperado.
Travas de arquivo (FileShare): Se abrir o arquivo com FileShare.None, outros processos não vão conseguir acessar. Isso pode ser ruim se outro programa (ou até outra thread do seu programa) tentar usar o mesmo arquivo. Sempre escolhe o modo FileShare mais adequado.
GO TO FULL VERSION