Como Ler um Arquivo Linha por Linha em Java
Leia um arquivo Java linha por linha com BufferedReader, Files.lines, Files.readAllLines e Scanner.
Ler um arquivo de texto uma linha por vez é uma das tarefas de arquivo mais comuns em Java. O JDK oferece várias formas de fazer isso; a escolha certa depende do tamanho do arquivo e se você prefere um loop simples ou um pipeline de stream. Este capítulo apresenta as quatro abordagens idiomáticas e quando usar cada uma.
BufferedReader: o motor de streaming
BufferedReader.readLine() lê uma única linha por chamada e retorna null no final do arquivo, combinando naturalmente com um loop while. Envolva-o em try-with-resources para que o reader subjacente se feche automaticamente:
Path file = Path.of("notes.txt");
try (BufferedReader br = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}Isso faz streaming do arquivo: apenas uma linha é mantida na memória por vez, de modo que ele lida com um log de vários gigabytes sem problemas. Files.newBufferedReader usa UTF-8 por padrão, mas passar o charset explicitamente documenta a intenção e evita decodificação dependente de plataforma. Note que readLine() remove o terminador de linha (\n, \r ou \r\n), por isso você nunca o vê na string retornada.
A forma try (...) acima é try-with-resources: ela garante que o reader seja fechado mesmo que o loop lance uma exceção. Veja buffered streams para entender por que envolver um reader bruto em um BufferedReader é importante para o desempenho.
Files.lines: o mesmo trabalho como um Stream
Quando você quer um pipeline funcional — filter, map, count — Files.lines fornece um Stream<String> lazy sobre as linhas do arquivo:
try (Stream<String> lines = Files.lines(Path.of("notes.txt"), StandardCharsets.UTF_8)) {
lines.filter(s -> !s.isBlank())
.map(String::trim)
.forEach(System.out::println);
}Assim como BufferedReader, ele lê de forma lazy e nunca carrega o arquivo inteiro. O detalhe é que o stream mantém um file handle aberto, portanto ele deve ser fechado — sempre use-o dentro de try-with-resources, nunca como uma expressão solta. Esquecer isso causa vazamento de descritores.
Files.readAllLines: arquivos pequenos, tudo de uma vez
Se o arquivo é pequeno e você quer todas as linhas em uma List<String> imediatamente, Files.readAllLines é a opção mais direta:
List<String> all = Files.readAllLines(Path.of("notes.txt"), StandardCharsets.UTF_8);
for (String line : all) {
System.out.println(line);
}É eager: o arquivo inteiro é decodificado para a memória antes que você acesse a primeira linha. Isso é conveniente para arquivos de configuração e fixtures, mas inadequado para arquivos grandes — prefira as abordagens de streaming nesses casos. Scanner com nextLine() e hasNextLine() é uma quarta opção, útil quando você também precisa analisar tokens, mas é mais lento e fácil de usar incorretamente, então os três acima cobrem a maioria dos casos. Para uma visão mais ampla de I/O de arquivos — abrir, ler arquivos inteiros e a API NIO Files — veja reading files in Java.
| Abordagem | Memória | Retorna | Melhor para |
|---|---|---|---|
BufferedReader.readLine() | Uma linha | Loop simples | Arquivos grandes, controle manual |
Files.lines() | Uma linha (lazy) | Stream<String> | Pipelines em arquivos grandes |
Files.readAllLines() | Arquivo inteiro | List<String> | Arquivos pequenos, acesso aleatório |
Scanner.nextLine() | Uma linha | Loop simples | Análise mista de linha + token |
Exemplo prático: os três lado a lado
Este programa escreve um pequeno arquivo temporário (para ser autocontido) e depois o lê de três formas — um loop com BufferedReader, um stream com Files.lines e um Files.readAllLines eager:
O que observar na execução:
- O loop com
BufferedReadernumera quatro linhas, e a linha3: []mostra que uma linha em branco no arquivo é retornada como uma string vazia, não ignorada —readLine()reporta cada linha, incluindo as vazias. readLine()imprimiu cada linha sem nenhum\nno final, confirmando que ele remove o terminador de linha; os únicos colchetes ao redor do texto são os literais[e]adicionados pelo código.Files.linescontounon-blank lines: 3porque ofilter(s -> !s.isBlank())descartou a linha vazia — o pipeline de stream opera de forma lazy sobre as mesmas quatro linhas que o loop viu.Files.readAllLinesreportoutotal lines: 4efirst line : alpha, provando que ele carregou o arquivo inteiro de forma eager em umaList<String>que você pode indexar comget(0).- Cada reader estava dentro de try-with-resources (ou retornou uma
Listgerenciada), portanto o file handle e o arquivo temporário foram liberados corretamente antes dedoneser impresso — sem vazamento de descritores.