Primeiras aulas do curso C# Parte 9: Entrada e saída (I/O) com streams

C# Parte 9: Entrada e saída (I/O) com streams

Lendo arquivos de texto - Introdução

Olá! Bem-vindo à mais um curso da Alura. Nesta edição, aprenderemos a lidar com entrada e saída por meio do C#. Foi disponibilizado para você um arquivo com várias contas correntes, enviado pelo nosso cliente Bytebank. Aprenderemos como fazer a leitura deste arquivo em nossa aplicação.

Precisamos lidar com o Stream, aprenderemos o que é isso e como utilizá-lo ao longo do curso. Utilizaremos buffers e manipularemos os intervalos a serem utilizados. A partir de um arquivo, recuperaremos uma série de bytes, o que não possui grande significado. O que queremos é recuperar o texto que acompanha que consiste em contas correntes e nomes.

472,5518,3068.83, Alessandra
143,5256,987.85, Ronaldo
361,3376,224.58, Larissa

Para efetuar a recuperação desse texto, teremos de fazer algumas transformações. Neste ponto entenderemos que são UTF, unicode e a transformação de bytes para caracteres, assim como o inverso, afinal criaremos o nosso arquivo, pois exportaremos as contas correntes que criamos na memória da aplicação. Desse modo, teremos de converter uma string para uma cadeia de bytes que será armazenado.

Aprenderemos a escrever em texto e em outros formatos, como binários, e ao final avaliaremos a diferença entre eles. Faremos tudo isso utilizando diretamente arquivos, mas também faremos uso de outros fluxos de entrada e saída, por exemplo da Console. Assim, conheceremos como ela opera, assim como os métodos readLine() e writeLine().

Temos bastante conteúdo pela frente, vamos lá?

Lendo arquivos de texto - Entendendo fluxo de dados

Já estamos com nosso ambiente preparado: o projeto está aberto no Visual Studio. Trata-se de um projeto simples, que possui um diretório chamado Modelos, que por sua vez abriga a classe Clinete.cs e ContaCorrete.cs. A segunda classe foi simplificada com relação àquela vista no último curso, assim podemos focar no que é importante para esta edição. O projeto foi disponibilizado nesta aula.

Nos cursos anteriores criávamos uma conta corrente do seguinte modo: criávamos uma variável Conta que recebe ContaCorrente

namespace ByteBankImportacaoExportacao
{ 
    class Program
    { 
        static void Main(string[] args)
        {
            var conta = new ContaCorrente

            Console
        }
    } 
} 

O problema ocorre quando precisamos preencher os argumentos do construtor. Precisamos incluir o número da agência e conta corrente, mas desse modo estamos fixando números em nosso código e a aplicação se torna nada dinâmica, afinal teremos sempre de utilizar os mesmos valores. Nosso objetivo é tornar a captação dos dados da conta corrente dinâmicos, recuperando as numerações como agência, conta, saldo e até mesmo o nome do titular por meio de um arquivo.

No caso, usaremo o arquivo contas.txt que contém várias contas bancárias. Em cada linha temos uma conta que é composta, respectivamente, por número da agência, número da conta corrente, saldo (possui um sinal de ponto que indica a quebra das casas decimais) e nome do titular.

375 4644 2483.13 Jonatan

Iremos criar instâncias de conta corrente na aplicação com base neste arquivo, que pode ser facilmente modificado caso necessário ao contrário do programa, que exigiria uma recompilação. Dessa forma, nossa aplicação se torna dinâmica.

Antes de começarmos de fato a escrever o código, é importante que reflitamos sobre qual será o procedimento que adotaremos, será que podemos coletar todo o conteúdo do arquivo contas.txt e armazenar em uma string e quebrando campo a campo? É uma alternativa, mas será que a memória do computador suporta todas essas informações de uma só vez?

Ao analisarmos as propriedades do arquivo contas.txt notaremos que ele ocupa 24,5KB, portanto é essa quantidade de memória que precisamos para manter o documento carregado na memória RAM. É um valor bem modesto perto dos 8GB disponível no computador, e até mesmo uma máquina mais modesta de 2GB serviria para este fim.

Tendo isso em vista, faz sentido carregar todo o arquivo na memória e processá-lo posteriormente, mas será que isso vale para todos os casos? Por exemplo, um vídeo em resolução 4K ocupa entre 20 a 30GB de espaço de armazenamento, e neste caso é muito mais do que a memória disponível do computador tradicional. De qualquer forma um vídeo com esse tamanho pode ser visualizado, mas como o MediaPlayer ou outros conseguem exibir um arquivo tão grande?

Nestes casos o arquivo não é inteiramente carregado na memória, processado e exibido. O que os players fazem é ler de pedaço em pedaço o arquivo e fazem o processamento - encoding, - por partes, isto é, converter uma sequência de bytes por algo que faça sentido, no caso, o som e imagem de um vídeo de forma processual. Portanto não é necessário carregar na memória 30GB de informações de uma vez.

Isso não vale apenas para um arquivo em HD, mas também para você que está assistindo este curso. Vejamos como exemplo a carreira de Xamarin disponível na Alura. Não escolhemos ela de forma aleatória, com o Xamarin podemos desenvolver aplicações para celular usando C#.

Dentro da carreira escolheremos o curso Curso Xamarin parte 3: crie aplicativos mobile com Visual Studio e clicaremos sobre um vídeo aleatório. Ao acionarmos o botão "play" o vídeo se inicia quase que imediatamente, lembrando que o vídeo está na resolução HD. O navegador não fez o download do vídeo completo, mas os bytes provenientes do servidor são consumidos em partes, ao passo que a imagem é exibida.

A ideia que estamos trabalhando é a seguinte: não lidamos com o arquivo completo para exibir dados ao usuário, e sim com fluxo de dados. Isso vale para um vídeo na internet, um filme em 4K e também para os arquivos de texto que usaremos em nosso projeto.

Dito isso, estamos prontos para começar nossa aplicação e escrever os códigos necessários que possibilitam o carregamento dinâmico do arquivo de contas bancárias.

Lendo arquivos de texto - Criando um FileStream

O primeiro passo é definir qual arquivo será lido. Neste caso, trata-se de uma string que representa o caminho deste arquivo. Usaremos uma variável enderecoDoArquivo que recebe uma string.

namespace ByteBankImportacaoExportacao
{

    class Program
    {
        static void Main(string[] args)
        {
            var enderecoDoArquivo = "";

            Console.ReadLine();
        }
    }
}

O arquivo contas.txt se encontra em "C:> Bytebank":

namespace ByteBankImportacaoExportacao
{

    class Program
    {
        static void Main(string[] args)
        {
            var enderecoDoArquivo = "c:/ByteBank/contas.txt";

            Console.ReadLine();
        }
    }
}

Se continuarmos nesse caminho, você será obrigado a criar esses diretórios no drive "C" em seu computador. Não é toda aluna ou aluno que possui um drive C, ou mesmo não deseja criar esses diretórios. Portanto podemos escrever nosso código de uma forma diferente: escrevendo a string apenas como contas.txt. Dessa forma, a aplicação buscará o arquivo no mesmo diretório que o executável .exe no HD.

Compilaremos a aplicação. Em seguida, acessaremos o diretório do projeto "bin > Debug" e teremos o arquivo ByteBankImportacaoExportacao.exe. Ao inserimos apenas contas.txt em nosso código, sem o endereço de diretório completo, será realizada uma busca de forma relativa ao diretório da aplicação, isto é, em Debug.

Passaremos o arquivo contas.txt para a pasta Debug, dessa forma ele se encontra no mesmo lugar que o arquivo excutável ByteBankImportacaoExportacao.exe.

Assim feito, precisamos criar o fluxo de bytes que possibilitará o acesso ao arquivo conta.txt, para então podermos percorar os bytes que o definem. Criaremos uma nova variável chamada fluxoDoArquivo, que receberá o fluxo, em inglês,stream. Contudo não estamos nos referindo a qualquer tipo de stream, assim como temos um fluxo de rede na internet, temos o fluxo de arquivos, portanto escreveremos FileStream.

namespace ByteBankImportacaoExportacao
{

    class Program
    {
        static void Main(string[] args)
        {
            var enderecoDoArquivo = "/contas.txt";

            var fluxoDoArquivo = new FileStream

            Console.ReadLine();
        }
    }
}

O Visual Studio reclama quando adicionamos FileStream, afinal está faltando a diretiva using. Neste caso, usaremos um namespace muito importante do .NET, o using System.IO, IO significa input output , isto é, "entrada e saída".

using System.IO; // IO = Input e Output

namespace ByteBankImportacaoExportacao
{

    class Program
    {
        static void Main(string[] args)
        {
            var enderecoDoArquivo = "/contas.txt";

            var fluxoDoArquivo = new FileStream

            Console.ReadLine();
        }
    }
}

O primeiro argumento que passaremos para FileStream é o enderecoDoArquivo. Em seguida, precisamos especificar o que faremos com o arquivo, queremos criar uma arquivo novo? Abrir um arquivo? Fazer uma concatenação? Precisamos fazer essa indicação quando criamos um FileStream , pois existem vários modos de operação possíveis. Escreveremos FileMode e indicaremos a abertura do arquivo (Open).

namespace ByteBankImportacaoExportacao
{

    class Program
    {
        static void Main(string[] args)
        {
            var enderecoDoArquivo = "/contas.txt";

            var fluxoDoArquivo = new FileStream(enderecoDoArquivo, FileMode.Open);

            Console.ReadLine();
        }
    }
}

Precisamos recuperar os bytes que estão neste arquivo. O FileStream possui um método chamado Read(), mas o que ele espera receber? Pressionaremos o "F12" para fazer essa investigação. Perceberemos que esse método espera por um bytes[] array:

public override int Read(byte[] array, int offset, int count); 

bytes[] array é onde será armazenado os bytes lidos pelo método. Em seguida, temos int offset, delimita o índice em que o método começará a preencher o array. Se colocarmos um valor 0, por exemplo, a array será preenchida desde começo, isto é, desde o índice 0. Caso mudemos esse valor para 10, os primeiros 10 bytes serão reservados para outro uso. Logo adiante temos o int count, que reflete quantos bytes deverão ser lidos e postos no array. Podemos criar uma array de 1000 posições, solicitar via int offset que a gravação de bytes ocorra a partir do índice 0 e colocar 10 em int count. Ao final teremos uma array de 1000 posicões, e apenas as primeiras 10 serão utilizadas para gravar os bytes do arquivo.

O array dado ao stream preencher possui um nome bastante comum na computação: buffer. Trata-se de um array que pode ser reutilizado para guardar informações temporárias. Criaremos uma variável buffer, que recebe um novo byte com [1024] posições, isto é, 1KB. Esse buffer será enviado para o método Read(), e queremos que o stream comece a gravar a partir da posição 0, até o fim, 1024.

namespace ByteBankImportacaoExportacao
{

    class Program
    {
        static void Main(string[] args)
        {
            var enderecoDoArquivo = "/contas.txt";

            var fluxoDoArquivo = new FileStream(enderecoDoArquivo, FileMode.Open);

            var buffer = new byte[1024]; // 1 kb

            fluxoDoArquivo.Read(buffer,0,1024);

            Console.ReadLine();
        }
    }
}

Agora escreveremos o que foi coletado desse arquivo. Para deixar o código mais elegante criaremos um método especializado em escrever o buffer na tela, que será o EscreverBuffer() e receberá como argumento um byte[] buffer. Começaremos a escrever foreach (var meuByte in buffer), e em seguida usaremos o método Write() e não WriteLine(), pois não queremos fazer uma linha para cada byte. Esse método receberá o parâmetro meuByte. Para separar um byte do outro, usaremos novamente Write(), que receberá um espaço em branco (" "). Feito isso, escreveremos EscreverBuffer() em nossa Console.

namespace ByteBankImportacaoExportacao
{

    class Program
    {
        static void Main(string[] args)
        {
            var enderecoDoArquivo = "/contas.txt";

            var fluxoDoArquivo = new FileStream(enderecoDoArquivo, FileMode.Open);

            var buffer = new byte[1024]; // 1 kb

            fluxoDoArquivo.Read(buffer,0,1024);
            EscreverBuffer(buffer);

            Console.ReadLine();
        }

        static void EscreverBuffer(byte[] buffer)
        {
            foreach (var meuByte in buffer)
            {
                Console.Write(meuByte);
                Console.Write(" ");
            }
        }
    }
}

Testaremos nossa aplicação. Visualizaremos uma série imensa de números, mas antes de analisarmos todos esses números, iremos executar mentalmente linha por linha do nosso código para termos clareza do que está acontecendo.

Configuramos o enderecoDoArquivo como conta.txt, isto é, um endereço relativo. Dessa forma você não é obrigado a criar um diretório específico na sua máquina. Adiante criamos um FileStream, que acessará o diretório em que está a aplicação (Debug). O modo de operação que utilizamos é o FileMode.Open, pois queremos simplesmente abrir um arquivo.

Criamos o buffer com capacidade de 1KB ou 1024 bytes, e escrevemos tudo na tela.

Os números impressos que temos quando executamos a aplicação não fazem muito sentido, afinal temos apenas uma cadeia de bytes, totalizando 1024. A ideia é que possamos transformar isso em texto. Contudo, devemos nos atentar para o fato de que só estão sendo lidos 1024 bytes e não o arquivo todo, que possui 24,5KB, ou seja, 25.160 bytes.

Será que devemos criar um array de 25.160 bytes? Não, afinal isso vai contra a ideia de não carregar o arquivo inteiro na memória, carregaremos apenas de pedaços em pedaços e processaremos isso conforme a aplicação vai sendo lida. Não precisamos saber o tamanho exato do arquivo e inserir essa informação no array.

Analisaremos com mais detalhes o método Read(), para isso, voltaremos à sua definição.

public override int Read(byte[] array, int offset, int count);

O método Read() retorna um int, e se olharmos na documentação na parte de "Devoluções", ou em inglês "Return" teremos:

// Devoluções:
//  O número total de bytes lidos do buffer. Isso poderá ser menor que o número de 
//  bytes solicitado se esse número de bytes não estiver disponível no momento, ou
//  zero, se o final do fluxo for atingido

Ou seja, se não houver 24,5 bytes será lido o valor inferior disponível, e o retorno poderá ser menor ou zero se o final do fluxo for atingido. Se lermos 1024, depois mais 1024 bytes e sobrar 10 bytes não lidos, será retornado 10 e na próxima tentativa de leitura será retornado o valor 0, pois chegamos ao final da stream.

Guardaremos o int retornado pelo método Read() e trabalharemos com ele. De volta à Program.cs, criaremso uma variável numeroDeBytesLidos e guardaremos o valor -1 , trata-se de um valor que nunca será retornado pelo Read(), afinal só pode haver números positivos ou zero, portanto temos uma inicialização segura.

Em seguida, atualizaremos o numeroDeBytesLidos conforme o retorno do Read(), portanto numeroDeBytesLidos receberá fluxoDoArquivo.Read(buffer, 0,1024).

namespace ByteBankImportacaoExportacao
{
<***!***>

            var buffer = new byte[1024]; // 1 kb
            var numeroDeBytesLidos = -1;

            numeroDeBytesLidos = fluxoDoArquivo.Read(buffer,0,1024);
            EscreverBuffer(buffer);

            Console.ReadLine();
        }

        static void EscreverBuffer(byte[] buffer)
        {
            foreach (var meuByte in buffer)
            {
                Console.Write(meuByte);
                Console.Write(" ");
            }
        }
    }
}

Precisaremos ler o arquivo até que ele chegue ao seu final, ou seja, até que o número de bytes lido ser zero. Portanto, usaremos o laço while(numeroDeBytesLidos !=0, enquanto o número de bytes lidos for diferente de zero, receberemos na variável numeroDeBytesLidos quantos bytes foram lidos e faremos o processamento, no caso, simplesmente escrever o resultado na tela.

namespace ByteBankImportacaoExportacao
{
<***!***>

            var buffer = new byte[1024]; // 1 kb
            var numeroDeBytesLidos = -1;

            while(numeroDeBytesLidos !=0)
            {
                numeroDeBytesLidos = fluxoDoArquivo.Read(buffer,0,1024);
                EscreverBuffer(buffer);

            }


            Console.ReadLine();
        }

        static void EscreverBuffer(byte[] buffer)
        {
            foreach (var meuByte in buffer)
            {
                Console.Write(meuByte);
                Console.Write(" ");
            }
        }
    }
}

Executaremos a aplicação, perceba que ela demora um pouco mais para ser concluída. O arquivo é composto pelos números que já conhecemos, e que ainda não fazem muito sentido, afinal queremos realmente trabalhar com texto. Mas antes, perceba o quão interessante está nossa aplicação: estamos reutilizando o buffer de 1KB para ler o arquivo, e depois ele é processado. Por mais que esse arquivo tenha 25KB, precisamso apenas de um arrey de 1KB. Essa lógica funcionou um arquivo pequeno, mas poderíamos fazer o mesmo processo com documentos maiores que o resultado seria o mesmo.

Agora que conseguimos recuperar a informações do HD, utilizar o buffer, e conhecemos o FileStream é chegado o memento de obtermos um texto a partir dos números.

Sobre o curso C# Parte 9: Entrada e saída (I/O) com streams

O curso C# Parte 9: Entrada e saída (I/O) com streams possui 155 minutos de vídeos, em um total de 39 atividades. Gostou? Conheça nossos outros cursos de .NET em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda .NET acessando integralmente esse e outros cursos, comece hoje!

  • 998 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

Premium

  • 998 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$75
à vista R$900
Matricule-se

Premium Plus

  • 998 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$100
à vista R$1.200
Matricule-se

Max

  • 998 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$120
à vista R$1.440
Matricule-se
Procurando planos para empresas?
Acesso por 1 ano
Estude 24h/dia onde e quando quiser
Novos cursos toda semana