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