W3docs

Leer archivos en Java

Lee archivos de texto y binarios en Java con FileReader, BufferedReader, Scanner, Files.readString y streams.

Hay cinco formas comunes de leer un archivo de texto en Java, y la elección correcta depende casi por completo del tamaño del archivo y de lo que quieras hacer con el contenido. Este capítulo recorre las cinco desde la más sencilla hasta la más flexible:

  1. Files.readString(path) — archivo completo como un String.
  2. Files.readAllLines(path) — archivo completo como una List<String>.
  3. Files.readAllBytes(path) — archivo completo como un byte[].
  4. Files.lines(path) — archivo como un Stream<String>, perezoso.
  5. BufferedReader / Scanner — decoradores clásicos, control total.

Elige la herramienta más pequeña que se ajuste a tus necesidades. Leer un log de 4 GB con Files.readString es un OutOfMemoryError; leer una configuración de 12 líneas con BufferedReader y un bucle while son seis líneas de código donde una bastaría.

Files.readString(path) — archivo completo, una sola llamada

String text = Files.readString(Path.of("config.json"), StandardCharsets.UTF_8);

Añadido en Java 11. Devuelve el archivo completo como un String. Usa UTF-8 por defecto desde Java 18 (aún se recomienda fijar el Charset explícitamente, incluso con el nuevo valor predeterminado). Lanza IOException si el archivo no existe o no puede leerse; lanza OutOfMemoryError si el archivo es más grande que el heap.

Úsalo cuando: el archivo sea "suficientemente pequeño" — archivos de configuración, payloads JSON, capítulos MDX, cualquier cosa que estés dispuesto a leer en una sola ventana del editor. La regla informal clásica es menos de unos pocos megabytes.

Files.readAllLines(path) — lista de líneas

List<String> lines = Files.readAllLines(Path.of("hosts.txt"), StandardCharsets.UTF_8);

Devuelve una List<String> inmutable de las líneas del archivo, con los terminadores de línea eliminados. Mismo perfil de memoria que readString más la sobrecarga de la List — también mantiene el archivo completo en memoria.

Úsalo cuando: quieras indexar por número de línea, ordenar el archivo o alimentar líneas en un bucle for (String line : lines) sin configurar streams.

Files.readAllBytes(path) — bytes en bruto

byte[] raw = Files.readAllBytes(Path.of("photo.png"));

El equivalente en bytes. Sin Charset porque no se produce ninguna decodificación. Úsalo para archivos binarios (imágenes, archivos comprimidos, ejecutables) o cuando necesites calcular un hash o canalizar bytes hacia un ByteArrayInputStream.

Files.lines(path) — stream perezoso

try (Stream<String> lines = Files.lines(Path.of("app.log"), StandardCharsets.UTF_8)) {
  long errors = lines.filter(l -> l.contains("ERROR")).count();
}

Este es el único lector integrado que escala a archivos de tamaño arbitrariamente grande. El Stream<String> es perezoso — las líneas se leen bajo demanda, no todas a la vez — y se conecta directamente al vocabulario de pipelines de stream (filter, map, count, toList).

Dos puntos no negociables:

  • try-with-resources es obligatorio. El stream posee un manejador de archivo abierto; sin try-with-resources, el archivo permanece abierto hasta que el GC actúe, y agotarás los descriptores de archivo en un servidor con mucha carga.
  • No reutilices el stream después de una operación terminal. Los streams son de un solo uso.

Úsalo cuando: el archivo sea demasiado grande para readAllLines, o quieras que la transformación línea a línea se componga con el resto de tu pipeline de stream.

BufferedReader.readLine() — el clásico

BufferedReader es el caballo de batalla que envuelven los helpers modernos. Almacena en búfer las lecturas subyacentes en un fragmento de memoria de tamaño fijo para que readLine() no emita una llamada al sistema por cada carácter.

try (BufferedReader in = Files.newBufferedReader(Path.of("hosts.txt"), StandardCharsets.UTF_8)) {
  String line;
  while ((line = in.readLine()) != null) {
    System.out.println(line);
  }
}

Files.newBufferedReader(path) es la fábrica moderna; la versión clásica es new BufferedReader(new FileReader("hosts.txt")) (que usa el charset de la plataforma en JDKs anteriores a la versión 18 — fíjalo a UTF-8 con la sobrecarga de tres argumentos). El contrato de readLine() es:

  • Devuelve la siguiente línea sin su terminador (\n, \r, o \r\n).
  • Devuelve null al final del archivo. La condición del bucle (line = readLine()) != null es el idioma establecido.

BufferedReader también es un productor de Stream<String>: reader.lines() devuelve un Stream<String> respaldado por el reader. Así es como se implementa Files.lines internamente.

Scanner — análisis token a token

Scanner lee texto por tokens — palabras, enteros, doubles, líneas, incluso coincidencias de regex — y es la herramienta adecuada para leer entradas estructuradas donde las unidades no son líneas completas.

try (Scanner sc = new Scanner(Files.newBufferedReader(Path.of("nums.txt")))) {
  while (sc.hasNextInt()) {
    int n = sc.nextInt();
    System.out.println(n * n);
  }
}

Scanner es más lento que BufferedReader porque analiza; asigna cadenas cortas y ejecuta regex. Para el procesamiento línea a línea, prefiere BufferedReader. Para tokens tipados de un archivo pequeño (números, palabras, entrada similar a CSV), Scanner ahorra la capa de análisis.

Hay un capítulo completo sobre Scanner más adelante en esta parte — este es el sabor de leer un archivo.

FileReader — el lector de caracteres en bruto

try (FileReader in = new FileReader("notes.txt", StandardCharsets.UTF_8)) {
  int c;
  while ((c = in.read()) != -1) {
    System.out.print((char) c);
  }
}

FileReader lee caracteres directamente del archivo — sin búfer, sin conciencia de líneas, sin elecciones de decodificación hechas por ti (pasas el Charset, o aceptas el predeterminado de la plataforma en JDKs anteriores a la versión 18). Es la capa sobre la que se asientan los demás. Casi nunca lo usas directamente en código de aplicación; lo envuelves en un BufferedReader.

Aún es útil cuando quieres leer unos pocos cientos de caracteres y detenerte — búsquedas pequeñas donde el costo de la configuración del búfer queda eclipsado por el costo de la llamada.

Cuál usar

EscenarioElige
Archivo pequeño que quieres como un único StringFiles.readString
Archivo pequeño que quieres como una List<String>Files.readAllLines
Archivo binario (imagen, archivo comprimido)Files.readAllBytes
Cualquier archivo con una transformación de estilo streamFiles.lines (dentro de try-with-resources)
Bucle línea a línea, control totalFiles.newBufferedReader + readLine
Tokens tipados (enteros, palabras, coincidencias de regex)Scanner
Un carácter a la vez, archivo pequeñoFileReader

El valor predeterminado correcto para el caso "solo quiero cargar este pequeño archivo de texto" es Files.readString. El valor predeterminado correcto para "procesar este log gigante sin agotar la memoria" es Files.lines.

Un ejemplo práctico: el mismo archivo, cinco lectores

El programa a continuación escribe un pequeño archivo de texto y luego lo lee de cinco maneras diferentes — readString, readAllLines, Files.lines filtrado a través de un Predicate<String> del vocabulario de la Parte 12, BufferedReader.readLine, y Scanner para enteros tokenizados. Cada bloque imprime lo que obtuvo para que puedas ver las formas una al lado de la otra.

java— editable, runs on the server

Lo que debes extraer de la ejecución:

  • Files.readString devolvió el archivo completo como un único String — fácil y exactamente lo que quieres para configuraciones y plantillas pequeñas. Para un log de 4 GB habría lanzado OutOfMemoryError.
  • Files.readAllLines devolvió una List<String> indexable con los terminadores eliminados. lines.get(0) funcionó porque la lista está materializada en memoria; no podrías hacer eso con un stream.
  • Files.lines(file) se abrió dentro de try-with-resources porque el stream posee el manejador de archivo. El pipeline .filter(isError).count() tiene la misma forma que cualquier cosa de la Parte 12 — solo cambió la fuente.
  • BufferedReader.readLine() devolvió null al final del archivo. El bucle for aquí se detuvo en tres a propósito, pero el idioma de producción es while ((line = in.readLine()) != null).
  • Scanner saltó las líneas que no comenzaban con un entero, luego leyó tokens con nextInt() hasta que se agotaron. El mismo Scanner podría haber leído doubles (nextDouble), coincidencias de regex (findInLine) o BigIntegers — por eso cuesta más por token que BufferedReader por línea.

Qué viene a continuación

El siguiente capítulo, Escribir archivos en Java, cubre el lado de escritura de las mismas APIs — Files.writeString, Files.write, BufferedWriter, PrintWriter y las banderas StandardOpenOption (APPEND, CREATE_NEW, TRUNCATE_EXISTING) que deciden cómo se maneja un archivo existente.

Práctica

Práctica
Necesitas procesar un log de servidor de 5 GB línea por línea, contando cuántas líneas contienen la palabra `ERROR`. ¿Cuál es el lector correcto?
Necesitas procesar un log de servidor de 5 GB línea por línea, contando cuántas líneas contienen la palabra `ERROR`. ¿Cuál es el lector correcto?
Was this page helpful?