Quando falamos sobre a leitura de arquivos, às vezes enfrentamos lentidão ou sobrecarga ao lidar com arquivos muito grandes. Foi por isso que surgiu a leitura em stream. Com isso, podemos reduzir o uso excessivo de recursos da máquina e garantir uma leitura contínua, evitando a perda de todo o trabalho em caso de erro.

Por padrão, a leitura em stream funciona pegando pequenas partes do arquivo para processar, o que permite manipular os dados à medida que são carregados. Em vez de carregar o arquivo inteiro de uma vez, a stream lê pequenos pedaços, como linhas ou mesmo segmentos separados por vírgulas. Isso torna a leitura mais eficiente, especialmente com arquivos muito grandes.

Além disso, a leitura em stream é vantajosa porque permite processar dados enquanto ainda estão sendo carregados, em vez de esperar até que todo o arquivo seja lido. Isso é útil para operações que podem ser realizadas em partes do arquivo, como filtragem, ordenação ou análise. Também reduz a necessidade de armazenar grandes quantidades de dados na memória de uma vez, o que pode ser crucial em sistemas com recursos limitados.

Em resumo, a leitura em stream é uma abordagem eficiente para lidar com arquivos grandes, permitindo uma utilização mais eficaz dos recursos do sistema e evitando problemas de desempenho relacionados à carga de grandes volumes de dados de uma só vez.


Usando Stream na Prática com Node.js

Agora que exploramos o conceito de streams, suas vantagens e funcionamento, vamos dar vida a essa teoria com um exemplo prático usando Node.js. Para facilitar a compreensão, preparei um repositório com uma API simples configurada com Express, TypeScript e Multer, para nos concentrarmos apenas em streams.

Para começarmos, vamos primeiro ler um arquivo de texto que tem várias linhas com o próprio NodeJs. Para isso, nós podemos criar uma stream usando o fs, pois ele permite usar stream para leitura e escrita. Nesse exemplo, vamos usar a leitura fs.createReadStream e passar o caminho do arquivo dentro da função:

app.get('/ler', async (req: Request, res: Response) => {

const filePath = path.join(__dirname, 'grandedados.txt');

if (!fs.existsSync(filePath)) {

return res.status(404).send('Arquivo não encontrado.');

}

res.setHeader('Content-Type','text/plain');

const readStream = fs.createReadStream(filePath);

});



É perceptível que a readStream tem várias propriedades que podemos usar. Vamos utilizar primeiro a pipe, que permite mandar funções, pegar o retorno da stream e escrever na resposta, conforme os dados. E, por último, podemos monitorar um evento como error (acionado quando algum erro foi identificado na stream):

app.get('/ler', async (req: Request, res: Response) => {

const filePath = path.join(__dirname, 'grandedados.txt');

if (!fs.existsSync(filePath)) {

return res.status(404).send('Arquivo não encontrado.');

}

res.setHeader('Content-Type','text/plain');

const readStream = fs.createReadStream(filePath);

readStream.pipe(res);

readStream.on('error', (err) => {

res.status(500).send(`Erro ao ler o arquivo: ${err.message}`);

    });

});

Vamos seguir com a leitura de um arquivo de texto com várias linhas usando Node.js. Para isso, podemos criar uma stream com o módulo fs, que nos permite ler e escrever arquivos usando streams. Neste exemplo, utilizaremos fs.createReadStream para ler o arquivo e passarmos o caminho do arquivo que queremos ler como argumento:

tsxCopy code

app.get('/ler', async (req: Request, res: Response) => {

const filePath = path.join(__dirname, 'grandedados.txt');

if (!fs.existsSync(filePath)) {

return res.status(404).send('Arquivo não encontrado.');

}

res.setHeader('Content-Type', 'text/plain');

const readStream = fs.createReadStream(filePath);

});

Após criar a stream de leitura, você pode observar que readStream possui várias propriedades úteis. Por exemplo, a função pipe nos permite direcionar os dados lidos diretamente para a resposta (res), enquanto a leitura é realizada. É importante notar que para pipe funcionar, o destino deve ser algo que possa receber dados escritos, como uma resposta HTTP (res).

Além disso, o método on pode ser usado para monitorar eventos na stream, como error, que é acionado caso ocorra algum problema durante a leitura.

Veja o exemplo atualizado com essas funcionalidades:

tsxCopy code

app.get('/ler', async (req: Request, res: Response) => {

const filePath = path.join(__dirname, 'grandedados.txt');

if (!fs.existsSync(filePath)) {

return res.status(404).send('Arquivo não encontrado.');

}

res.setHeader('Content-Type', 'text/plain');

const readStream = fs.createReadStream(filePath);

readStream.pipe(res);

readStream.on('error', (err) => {

res.status(500).send(`Erro ao ler o arquivo: ${err.message}`);

    });

});

Esse exemplo demonstra como usar pipe para enviar os dados lidos pela stream diretamente à resposta, e on para lidar com possíveis erros durante a leitura. Essa abordagem torna o processo de leitura de arquivos em Node.js eficiente e fácil de implementar.

Por fim, vamos explorar a escrita com streams em Node.js! Assim como a leitura, a escrita com streams pode ser eficiente e ajuda a economizar memória RAM, já que não precisamos carregar o arquivo inteiro na memória.

No exemplo abaixo, criamos um arquivo chamado bigfile.txt com 1 milhão de linhas de texto gerado com uma simples repetição (for). Cada iteração do loop adiciona uma linha de texto à stream de escrita, que grava diretamente no arquivo:

tsxCopy code

app.post('/escrita', async (req, res) => {

const stream = fs.createWriteStream('./bigfile.txt');

const maxLines = 1e6; // 1 milhão

// Gerando 1 milhão de linhas de texto e escrevendo na stream

for (let i = 0; i < maxLines; i++) {

stream.write(`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam ac enim cursus, venenatis eros in, gravida massa.\\n`);

}

// Encerrando a stream após terminar a escrita

stream.end();

// Retornando sucesso

res.status(201).json({ mensagem: 'Arquivo criado com sucesso!' });

});


Nesse exemplo, ao criar uma stream de escrita (fs.createWriteStream), conseguimos adicionar texto ao arquivo enquanto percorremos o loop, economizando recursos de memória. Após terminar de escrever, encerramos a stream com stream.end().

E aí, curtiu a dica?

Continue acompanhando nosso Blog para mais aprendizados!

*Texto produzido por Amanda Rodrigues, Embaixadora da ComuniCubos