CodeGym /Cursos /C# SELF /Lendo arquivos de texto: S...

Lendo arquivos de texto: StreamReader

C# SELF
Nível 36 , Lição 2
Disponível

1. Introdução

Imagina que a gente precisa de novo ler o conteúdo de um arquivo de texto. Suponha que temos um arquivo com citações legais de programadores e queremos mostrar isso no nosso app de texto já conhecido. Sim, já vimos métodos de alto nível tipo File.ReadAllText, mas e se o arquivo for grande e a gente quiser ler ele linha por linha? Ou se a gente quiser ler o arquivo de forma controlada, tipo carregando devagar as linhas pra não estourar a memória?

É aí que entra o StreamReader — uma classe perfeita pra ler arquivos de texto linha por linha, em blocos, caractere por caractere — enfim, do jeito que você quiser, bem flexível e prático.

Como funciona o StreamReader?

StreamReader é uma classe do namespace System.IO que faz a leitura de dados de texto de um stream (normalmente um arquivo, mas pode ser um stream de rede ou memória também). Ele sabe ler os dados na codificação certa, converte pra caracteres e linhas, e te devolve tipos de dados fáceis de usar: strings e chars.

Se a gente desenhar isso num esquema, fica tipo assim:


[ Arquivo (bytes) ] --(FileStream)--> [ StreamReader (decodifica os códigos dos caracteres) ] ---> [ Seu código (string, char) ]
  • Arquivo (ou outra fonte de bytes): Fornece os bytes pra gente.
  • FileStream: Lê esses bytes, tipo um "canal".
  • StreamReader: Transforma os bytes em caracteres e linhas, levando em conta a codificação (por padrão — UTF-8).

Por que nem sempre vale a pena usar só o File.ReadAllText?

Na vida real, os arquivos podem ser grandes. Tipo, muito grandes (logs ou csv com um milhão de linhas, por exemplo). Tentar ler tudo de uma vez pra memória é tipo tentar comer um bolo inteiro de uma vez: gostoso, mas perigoso pra saúde.
O StreamReader lê o arquivo "em pedaços". Ele não joga tudo na memória, só carrega e devolve a próxima linha ou caractere quando você pede. Isso economiza memória e é muito prático.

2. Lendo o arquivo inteiro linha por linha

Vamos criar um arquivo quotes.txt com essas linhas:


Código — é poesia.
Debug — é investigação.
"Não funciona" — melhor bug report.

Agora vamos colocar esse código no nosso app de estudo (pode ser um programa de console simples que a gente tá melhorando aos poucos). Coloca o arquivo do lado do .exe — aí o programa lê e mostra o conteúdo linha por linha.

Código com comentários:


// Pegando o caminho do arquivo na pasta do programa
string fileName = "quotes.txt";
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);

// Checando se o arquivo existe
if (!File.Exists(filePath))
{
    Console.WriteLine($"Arquivo não encontrado: {filePath}");
    return;
}

// Abrindo o arquivo pra leitura usando StreamReader
using StreamReader reader = new StreamReader(filePath);
string? line;
int lineNumber = 1;

// Lendo o arquivo linha por linha até o final
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine($"{lineNumber,2}: {line}");
    lineNumber++;
}

O que tá rolando aqui?

  • A gente monta o caminho do arquivo usando Path.Combine e AppDomain.CurrentDomain.BaseDirectory — assim o código funciona igual no Windows, Linux e até em Docker.
  • Primeiro, checa se o arquivo existe mesmo.
  • Com o using a gente cria o StreamReader e lê o arquivo linha por linha (método ReadLine()).
  • No final do bloco using o arquivo é fechado, mesmo se você esquecer de fechar ou se der erro.

Visualização: esquema de funcionamento


[ quotes.txt ] --(FileStream)--> [ StreamReader ] --(ReadLine)--> [ string line ] --(Console.WriteLine)--> Tela
    ^                                                       
    |
AppDomain.CurrentDomain.BaseDirectory + Path.Combine

3. Detalhes e pegadinhas do uso

Como funciona a leitura das linhas

O método ReadLine() devolve a string até o primeiro caractere de nova linha (\n ou \r\n). Quando o arquivo acaba, retorna null. Por isso o loop normalmente é assim:


string? line;
while ((line = reader.ReadLine()) != null)
{
    // processa a linha
}

Erros comuns usando StreamReader

Às vezes bate a tentação de não usar o using e só fazer assim:


StreamReader reader = new StreamReader(filePath);
// ...
reader.Close();

Esse jeito é tipo esquecer de fechar a porta no frio: pode dar ruim. Se der erro lendo o arquivo (tipo o disco some do nada), o código pra fechar o arquivo não roda e o arquivo fica aberto no sistema.
Por isso, usar using não é só dica, é necessidade real. O sysadmin do futuro vai te agradecer porque seu programa não deixa um monte de arquivos "presos" abertos.

Jeito curto usando using declaration

Com as versões mais novas do C# (desde a 8.0) dá pra escrever mais curto:


using StreamReader reader = new StreamReader(filePath);
string? line;
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine(line);
}

O Dispose é chamado no fim do bloco (do método).

4. Dicas úteis

Lendo arquivo com codificação desconhecida

Por padrão, o StreamReader usa UTF-8. Mas às vezes aparecem arquivos com outra codificação (tipo Windows-1251 pra textos antigos em russo). Nesse caso, o programa pode mostrar "?" ou caracteres estranhos.

Dá pra especificar a codificação assim:


using System.Text;

// Abrindo arquivo como Windows-1251
using StreamReader reader = new StreamReader(filePath, Encoding.GetEncoding("windows-1251"));

Documentação sobre codificações e Encoding

Lendo o arquivo como um texto grandão

Às vezes você quer pegar o arquivo inteiro como texto, mas não usando File.ReadAllText, e sim o StreamReader.


using StreamReader reader = new StreamReader(filePath);
string allText = reader.ReadToEnd();
Console.WriteLine(allText);
  • O método ReadToEnd() lê tudo do arquivo da posição atual até o final como uma string só.
  • Pra arquivos grandes isso pode ser ruim e pode dar OutOfMemoryException. Pra arquivos de alguns megas, tranquilo.

Como isso aparece na vida real

  • Processando logs. Se você tem um arquivo de log do servidor, é fácil ler linha por linha, filtrar eventos e não carregar tudo na memória.
  • Importando CSV. Se precisa ler tabelas grandes, é prático pegar linha por linha e processar conforme chega.
  • Procurando palavras-chave em arquivos grandes. Dá pra buscar texto entre milhões de linhas sem medo de estourar a memória.
  • Testes unitários. Arquivos de dados de teste geralmente são lidos linha por linha com StreamReader.

Comparando métodos de leitura

Jeito Quando usar Vantagens Desvantagens
File.ReadAllText
Arquivos pequenos Uma linha de código, rápido Arquivo grande – muita memória
StreamReader.ReadLine()
Qualquer arquivo, principalmente grandes Lê linha por linha, pouca memória Um pouco mais de código, lógica mais trabalhosa
StreamReader.ReadToEnd()
Arquivos pequenos/médios Dá pra controlar a codificação Pesa com arquivos muito grandes

5. Prática

Vamos deixar o exercício mais interessante. Agora o programa vai mostrar só as primeiras N linhas do arquivo, sendo que o usuário digita quantas quer ver. Concorda que às vezes não precisa ver centenas de linhas — só umas citações já motivam. :)

Olha como faz:


using System;
using System.IO;

class Program
{
    static void Main()
    {
        string fileName = "quotes.txt";
        string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);

        if (!File.Exists(filePath))
        {
            Console.WriteLine($"Arquivo não encontrado: {filePath}");
            return;
        }

        Console.Write("Quantas linhas mostrar? ");
        string? input = Console.ReadLine();
        if (!int.TryParse(input, out int linesToShow) || linesToShow < 1)
        {
            Console.WriteLine("Erro: Digite um número válido maior que 0.");
            return;
        }

        using StreamReader reader = new StreamReader(filePath);

        int current = 0;
        string? line;
        while (current < linesToShow && (line = reader.ReadLine()) != null)
        {
            Console.WriteLine(line);
            current++;
        }

        if (current == 0)
            Console.WriteLine("Arquivo vazio ou não deu pra ler a primeira linha.");
        else if (current < linesToShow)
            Console.WriteLine($"O arquivo tinha só {current} linha(s).");
    }
}

6. Pontos importantes e pegadinhas comuns

Quando você usa o StreamReader, o principal é lembrar de gerenciar os recursos direito (vê a aula anterior sobre IDisposable). Se esquecer disso, pode rolar erro "arquivo já está sendo usado por outro processo" ou "muitos arquivos abertos".

Outro detalhe — cuidado com a codificação. Se não tem certeza que o arquivo é UTF-8, coloca a codificação certa. Pra isso serve o segundo parâmetro do construtor do StreamReader.

Mais uma pegadinha prática: se o arquivo for atualizado enquanto o programa roda (tipo um log), nem sempre dá pra ver as linhas novas logo de cara se o arquivo tá sendo usado por outros processos — aí tem que reler/reabrir o arquivo ou usar métodos especiais, mas isso é papo pra depois.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION