Cómo leer un archivo línea por línea en Java
Lee un archivo Java línea por línea con BufferedReader, Files.lines, Files.readAllLines y Scanner.
Leer un archivo de texto una línea a la vez es una de las tareas de archivo más comunes en Java. El JDK ofrece varias formas de hacerlo; la elección correcta depende del tamaño del archivo y de si prefieres un bucle sencillo o un pipeline de stream. Este capítulo muestra los cuatro enfoques idiomáticos y cuándo usar cada uno.
BufferedReader: el motor de streaming
BufferedReader.readLine() lee una sola línea por llamada y devuelve null al final del archivo, por lo que se combina de forma natural con un bucle while. Envuélvelo en try-with-resources para que el lector subyacente se cierre automáticamente:
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);
}
}Esto transmite el archivo en streaming: solo se mantiene una línea en memoria a la vez, por lo que puede manejar un log de varios gigabytes sin problemas. Files.newBufferedReader usa UTF-8 por defecto, pero pasar el charset explícitamente documenta la intención y evita la decodificación dependiente de la plataforma. Ten en cuenta que readLine() elimina el terminador de línea (\n, \r o \r\n), por lo que nunca aparece en la cadena devuelta.
La forma try (...) anterior es try-with-resources: garantiza que el lector se cierre incluso si el bucle lanza una excepción. Consulta streams con búfer para entender por qué envolver un lector sin búfer en un BufferedReader importa para el rendimiento.
Files.lines: el mismo trabajo como Stream
Cuando quieres un pipeline funcional —filtrar, mapear, contar— Files.lines te proporciona un Stream<String> perezoso sobre las líneas del archivo:
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);
}Al igual que BufferedReader, lee de forma perezosa y nunca carga el archivo completo. El inconveniente es que el stream mantiene un descriptor de archivo abierto, por lo que debe cerrarse — úsalo siempre dentro de try-with-resources, nunca como una expresión suelta. Olvidar esto provoca fugas de descriptores.
Files.readAllLines: archivos pequeños, todos a la vez
Si el archivo es pequeño y quieres todas las líneas en una List<String> desde el principio, Files.readAllLines es la opción más directa:
List<String> all = Files.readAllLines(Path.of("notes.txt"), StandardCharsets.UTF_8);
for (String line : all) {
System.out.println(line);
}Es ávido: el archivo completo se decodifica en memoria antes de que accedas a la primera línea. Eso es conveniente para archivos de configuración y fixtures, pero no es adecuado para archivos grandes — en esos casos prefiere los enfoques de streaming. Scanner con nextLine() y hasNextLine() es una cuarta opción, útil cuando también necesitas analizar tokens, pero es más lento y fácil de usar incorrectamente, por lo que los tres anteriores cubren la mayoría de los casos. Para una visión más amplia de la E/S de archivos — apertura, lectura de archivos completos y la API NIO Files — consulta leer archivos en Java.
| Enfoque | Memoria | Devuelve | Mejor para |
|---|---|---|---|
BufferedReader.readLine() | Una línea | Bucle simple | Archivos grandes, control manual |
Files.lines() | Una línea (perezoso) | Stream<String> | Pipelines en archivos grandes |
Files.readAllLines() | Archivo completo | List<String> | Archivos pequeños, acceso aleatorio |
Scanner.nextLine() | Una línea | Bucle simple | Análisis mixto de líneas y tokens |
Ejemplo práctico: los tres enfoques juntos
Este programa escribe un pequeño archivo temporal (para que sea autónomo) y luego lo lee de tres formas — un bucle con BufferedReader, un stream con Files.lines y un Files.readAllLines ávido:
Qué observar al ejecutarlo:
- El bucle con
BufferedReadernumera cuatro líneas, y la línea3: []muestra que una línea en blanco en el archivo se devuelve como una cadena vacía, no se omite —readLine()reporta cada línea, incluyendo las vacías. readLine()imprimió cada línea sin ningún\nal final, confirmando que elimina el terminador de línea; los únicos corchetes alrededor del texto son los literales[y]que agregó el código.Files.linescontónon-blank lines: 3porque elfilter(s -> !s.isBlank())descartó la línea vacía — el pipeline de stream opera de forma perezosa sobre las mismas cuatro líneas que vio el bucle.Files.readAllLinesreportótotal lines: 4yfirst line : alpha, demostrando que cargó todo el archivo de forma ávida en unaList<String>que puedes indexar conget(0).- Cada lector estaba dentro de try-with-resources (o devolvía una
Listgestionada), por lo que el descriptor de archivo y el archivo temporal se liberaron limpiamente antes de que se imprimieradone— sin descriptores filtrados.