W3docs

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.

EnfoqueMemoriaDevuelveMejor para
BufferedReader.readLine()Una líneaBucle simpleArchivos grandes, control manual
Files.lines()Una línea (perezoso)Stream<String>Pipelines en archivos grandes
Files.readAllLines()Archivo completoList<String>Archivos pequeños, acceso aleatorio
Scanner.nextLine()Una líneaBucle simpleAná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:

java— editable, runs on the server

Qué observar al ejecutarlo:

  • El bucle con BufferedReader numera cuatro líneas, y la línea 3: [] 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 \n al 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.lines contó non-blank lines: 3 porque el filter(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.readAllLines reportó total lines: 4 y first line : alpha, demostrando que cargó todo el archivo de forma ávida en una List<String> que puedes indexar con get(0).
  • Cada lector estaba dentro de try-with-resources (o devolvía una List gestionada), por lo que el descriptor de archivo y el archivo temporal se liberaron limpiamente antes de que se imprimiera done — sin descriptores filtrados.

Práctica

Práctica
¿Por qué debe usarse un stream devuelto por Files.lines() dentro de un bloque try-with-resources?
¿Por qué debe usarse un stream devuelto por Files.lines() dentro de un bloque try-with-resources?
Was this page helpful?