Ir al contenido
wannacode

Archivos

En resumen: Un archivo guarda los datos en el disco incluso después de que el programa termina; en Java Path y Files leen y escriben texto, y try-with-resources cierra el archivo por ti.

Problema

El programa olvida

Estás escribiendo un programa para tus gastos. Los anotas por la noche, lo cierras, y al día siguiente no sabe nada de ayer. Aquí está el ejemplo más corto:

int visits = 0;
visits++;
System.out.println("Ejecutado " + visits + " vez.");
Salida
Ejecutado 1 vez.

Da igual cuántas veces lo ejecutes: siempre dice «1 vez». Las variables viven en la memoria mientras el programa funciona. Cuando termina, la memoria se libera y todo desaparece.

Para recordar, el programa tiene que escribir los datos donde se quedan después de que termine: en un archivo en el disco.

La idea

Ruta y archivo

Aquí tienes un programa completo que escribe una nota en un archivo y la vuelve a leer:

Main.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) {
Path notes = Path.of("notes.txt");
try {
Files.writeString(notes, "Comprar leche\nPagar la luz antes del 15\n");
String text = Files.readString(notes);
System.out.print(text);
} catch (IOException e) {
System.out.println("Problema con el archivo: " + e.getMessage());
}
}
}
Salida
Comprar leche
Pagar la luz antes del 15
  • Path es la ruta al archivo: «dónde está». Path.of("notes.txt") es un archivo con ese nombre en la carpeta desde la que se ejecutó el programa.
  • Files tiene métodos estáticos para todo lo que tiene que ver con archivos. writeString escribe texto (y crea el archivo si no existe), readString lo lee entero.
  • IOException es una excepción comprobada, del módulo sobre excepciones. El disco puede estar lleno, el archivo puede estar bloqueado o no existir, y el compilador te obliga a decir qué debe pasar entonces.

Ejecuta el programa y abre la carpeta: notes.txt está ahí, y puedes abrirlo con cualquier editor de texto. La próxima ejecución lo encontrará.

En los ejemplos que siguen no se muestran los import. Todo viene de java.nio.file y java.io, y List viene de java.util.

La idea

Añade y lee las líneas

Los gastos se añaden uno por uno. StandardOpenOption.APPEND añade al final, en lugar de escribir el archivo desde cero. readAllLines lee el archivo como líneas:

Path expenses = Path.of("expenses.txt");
try {
Files.writeString(expenses, "Almuerzo;12.50\n");
Files.writeString(expenses, "Taxi;8.20\n", StandardOpenOption.APPEND);
Files.writeString(expenses, "Libro;18.90\n", StandardOpenOption.APPEND);
List<String> lines = Files.readAllLines(expenses);
double total = 0;
for (String line : lines) {
String[] parts = line.split(";");
total += Double.parseDouble(parts[1]);
System.out.println(parts[0] + ": " + parts[1] + " €");
}
System.out.printf("%d gastos, total %.2f €%n", lines.size(), total);
} catch (IOException e) {
System.out.println("Problema con el archivo: " + e.getMessage());
}
Salida
Almuerzo: 12.50 €
Taxi: 8.20 €
Libro: 18.90 €
3 gastos, total 39.60 €

readAllLines devuelve una List<String>: una lista de cadenas, una por cada línea. Las listas están en el siguiente módulo, «Colecciones». Por ahora solo necesitas dos cosas: recorrerla con for-each, como un array, y size() para el número de líneas.

Más precisamente, para curiosos
  • APPEND por sí solo no crea el archivo. Si no existe, obtienes NoSuchFileException. Para «crear o añadir», pasa las dos opciones: StandardOpenOption.CREATE, StandardOpenOption.APPEND.
  • Desde Java 18, el texto en los archivos está en UTF-8 por defecto, y letras como «ñ», «é» y «€» se leen y se escriben bien. En versiones anteriores dependía de la configuración de la computadora. Si lees un archivo de otro sistema, puedes indicar la codificación de forma explícita: Files.readString(path, StandardCharsets.UTF_8).
  • readAllLines y readString leen todo el archivo en la memoria. Para un archivo de varios gigabytes es mejor leer línea por línea. Mira la tarjeta sobre BufferedReader.

Pruébalo

Dos turnos

¿Qué imprimirá el programa?

Path shift = Path.of("shift.txt");
try {
Files.writeString(shift, "Primer turno: Carlos\n");
Files.writeString(shift, "Segundo turno: Lucía\n");
System.out.print(Files.readString(shift));
} catch (IOException e) {
System.out.println(e.getMessage());
}
Respuesta

La idea

Cuando el archivo no está

La configuración del programa está en config.txt, y alguien lo borró. Si nadie captura la excepción:

Main.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
static String readConfig() throws IOException {
return Files.readString(Path.of("config.txt"));
}
public static void main(String[] args) throws IOException {
System.out.println(readConfig());
}
}
Error
Exception in thread "main" java.nio.file.NoSuchFileException: config.txt
at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92)
...
at java.base/java.nio.file.Files.readString(Files.java:3318)
at Main.readConfig(Main.java:7)
at Main.main(Main.java:11)

Las líneas de java.base son el interior de Java (aquí están abreviadas con «...»). Tu código está abajo del todo: la línea 7 en readConfig, llamado desde la línea 11 en main. main también ha declarado throws IOException, así que la excepción llega a la JVM y el programa se detiene.

NoSuchFileException es una subclase de IOException. Captúrala aparte, antes del caso general:

static String readConfig() throws IOException {
return Files.readString(Path.of("config.txt"));
}
try {
System.out.println(readConfig());
} catch (NoSuchFileException e) {
System.out.println("No hay archivo " + e.getMessage() + ". Uso la configuración por defecto.");
} catch (IOException e) {
System.out.println("No puedo leer la configuración: " + e.getMessage());
}
System.out.println(Files.exists(Path.of("config.txt")));
Salida
No hay archivo config.txt. Uso la configuración por defecto.
false

Files.exists(path) comprueba si el archivo existe. La comprobación no reemplaza a catch: el archivo puede desaparecer entre la comprobación y la lectura, y hay otras razones por las que la lectura puede fallar.

La idea

Línea por línea y try-with-resources

Para un archivo grande, o para escribir por partes, están BufferedWriter y BufferedReader. Mantienen el archivo abierto mientras trabajas con él, así que hay que cerrarlos al final:

Path report = Path.of("report.txt");
try (BufferedWriter out = Files.newBufferedWriter(report)) {
for (int day = 1; day <= 3; day++) {
out.write("Día " + day + ": " + day * 120 + " €");
out.newLine();
}
} catch (IOException e) {
System.out.println("No puedo escribir: " + e.getMessage());
}
try (BufferedReader in = Files.newBufferedReader(report)) {
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("No puedo leer: " + e.getMessage());
}
Salida
Día 1: 120 €
Día 2: 240 €
Día 3: 360 €
  • write escribe texto, newLine() pasa a una línea nueva.
  • readLine() devuelve la siguiente línea, o null cuando el archivo se termina.
  • try (...) con un recurso creado entre los paréntesis es try-with-resources. Cuando el bloque termina, sea como sea, el recurso se cierra solo.

Cerrar no es un trámite: mientras el BufferedWriter está abierto, el texto puede estar todavía en su búfer, no en el disco. Para que el código sea corto, aquí main tiene throws IOException:

Path report = Path.of("report.txt");
try (BufferedWriter out = Files.newBufferedWriter(report)) {
for (int day = 1; day <= 3; day++) {
out.write("Día " + day + ": " + day * 120 + " €");
out.newLine();
}
System.out.println("En el bloque: " + Files.size(report) + " bytes");
}
System.out.println("Después del bloque: " + Files.size(report) + " bytes");
for (String line : Files.readAllLines(report)) {
System.out.println(line);
}
Paso a paso, en palabras
  1. Path es solo la dirección del archivo. A la izquierda está la memoria del programa, a la derecha el disco.
  2. newBufferedWriter crea el archivo vacío report.txt y un objeto con un búfer en la memoria.
  3. write y newLine escriben en el búfer. Al disco todavía no llega nada.
  4. El archivo en el disco todavía tiene 0 bytes.
  5. El bloque termina y try llama a close(). El búfer va al disco: el archivo pasa a tener 48 bytes.
  6. 48 bytes para 39 caracteres con los saltos de línea: en UTF-8 cada letra latina sin tilde ocupa 1 byte, la «í» ocupa 2 y el € ocupa 3.
  7. readAllLines vuelve a leer las tres líneas del disco.

Lo prometimos en la lección sobre finally. Aquí está la misma escritura con finally:

BufferedWriter out = null;
try {
out = Files.newBufferedWriter(report);
out.write("Día 1: 120 €");
} catch (IOException e) {
System.out.println("No puedo escribir: " + e.getMessage());
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
System.out.println("No puedo cerrar: " + e.getMessage());
}
}
}

Quince líneas para una sola escritura, y close() también puede lanzar una IOException. try (...) hace exactamente esto por ti.

Más precisamente, para curiosos
  • Entre los paréntesis puede haber varios recursos, separados con ;. Se cierran en orden inverso: el último que se abrió se cierra primero.
  • Búfer. BufferedWriter reúne el texto en la memoria y lo envía al disco en partes grandes. Cada escritura separada en el disco es lenta, y así hay pocas. Del mismo modo, BufferedReader lee mucho de una vez.
  • Bytes y caracteres. Por debajo, un archivo es una secuencia de bytes. InputStream y OutputStream (por ejemplo Files.newInputStream) leen y escriben bytes: para fotos, archivos comprimidos, todo lo que no es texto. Reader y Writer convierten los bytes en caracteres según la codificación. BufferedReader es un Reader con búfer.
  • La forma antigua. En el código de otras personas verás new BufferedReader(new FileReader("report.txt")) y new Scanner(new File("report.txt")). Funcionan, pero Files es más nuevo y más cómodo. Scanner también lee de un archivo, con los hasNextLine y nextLine que ya conoces:
try (Scanner in = new Scanner(Path.of("report.txt"))) {
while (in.hasNextLine()) {
System.out.println(in.nextLine());
}
}

Pruébalo

¿Cuándo se cierra?

Cualquier clase que implemente la interfaz AutoCloseable (el método close()) puede ir en un try (...). Aquí está la impresora de la caja de la lección sobre finally, como recurso:

¿Qué imprimirá el programa?

class Printer implements AutoCloseable {
Printer() {
System.out.println("La impresora está ocupada.");
}
public void close() {
System.out.println("La impresora está libre.");
}
}
try (Printer printer = new Printer()) {
System.out.println("Imprimiendo el recibo...");
int qty = Integer.parseInt("tres");
System.out.println("Cantidad: " + qty);
} catch (NumberFormatException e) {
System.out.println("Cantidad incorrecta.");
}
Respuesta

Pruébalo

Encuentra el error

El programa escribe un pedido y cuenta las líneas del archivo. Imprime 0. ¿En qué línea está el error?

Elige la línea con el error.

La idea

Carpetas

Las copias de seguridad de la caja van a una carpeta, una por día:

Path backup = Path.of("backup");
try {
Files.createDirectories(backup.resolve("old"));
Files.writeString(backup.resolve("2026-10-01.csv"), "Café,2.40\n");
Files.writeString(backup.resolve("2026-10-02.csv"), "Café,2.40\nTé,1.80\n");
String[] names = backup.toFile().list();
Arrays.sort(names);
for (String name : names) {
Path p = backup.resolve(name);
if (Files.isDirectory(p)) {
System.out.println(name + "/");
} else {
System.out.println(name + " (" + Files.size(p) + " bytes)");
}
}
Files.delete(backup.resolve("2026-10-01.csv"));
System.out.println(Files.exists(backup.resolve("2026-10-01.csv")));
} catch (IOException e) {
System.out.println(e.getMessage());
}
Salida
2026-10-01.csv (11 bytes)
2026-10-02.csv (20 bytes)
old/
false
Método Qué hace
Files.createDirectories(p) crea la carpeta y todas las carpetas por encima de ella, si no existen
dir.resolve("nombre") la ruta a nombre dentro de dir
Files.isDirectory(p), Files.exists(p) si es una carpeta, si existe
Files.size(p) el tamaño en bytes
Files.delete(p) borra un archivo o una carpeta vacía
dir.toFile().list() un array con los nombres de todo lo que hay en la carpeta

Los tamaños son 11 y 20 bytes, aunque los caracteres son menos: en UTF-8, la «é» de «Café» y de «Té» ocupa 2 bytes.

list() da los nombres sin ningún orden concreto, así que los ordenamos con java.util.Arrays.sort.
Más precisamente, para curiosos
  • Files.delete lanza DirectoryNotEmptyException si la carpeta no está vacía, y NoSuchFileException si no existe. Files.deleteIfExists no se queja si no existe.
  • Files.list(dir) es la forma nueva de obtener el contenido de una carpeta, pero devuelve un Stream, que está en el módulo «Java moderno». Files.copy y Files.move copian y mueven.
  • En código más antiguo verás la clase java.io.File: new File("notes.txt") con los métodos exists(), length(), delete(), renameTo(...), createNewFile() y getAbsolutePath(). Existía antes que Path y Files, y path.toFile() y file.toPath() convierten uno en otro. Allí, añadir al final es new FileWriter("output.txt", true).
  • La ruta "notes.txt" es relativa: a la carpeta desde la que se ejecutó el programa. Path.of("/home/ana/notes.txt") es absoluta. path.toAbsolutePath() muestra exactamente dónde está el archivo.

Ejercicio

Programas que recuerdan

Resuelve las tareas en tu editor. En cada tarea, el programa primero crea él mismo el archivo de entrada con Files.writeString, para que puedas probarlo enseguida.

1. Un archivo de configuración. Crea server.conf con este contenido, después léelo e imprime cada ajuste como clave: valor. Sáltate las líneas vacías y las líneas que empiezan con #. Pista: split("=", 2) divide solo en el primer =.

server.conf
# configuración del servidor
max_connections=500
log_file_path=/var/log/server.log
db_timeout=30
Salida
max_connections: 500
log_file_path: /var/log/server.log
db_timeout: 30

2. Un filtro de archivo de registro. Crea app.log con cuatro líneas (dos INFO y dos ERROR). Lee un nivel del teclado, por ejemplo ERROR, y escribe las líneas con ese nivel en errors.log con un BufferedReader y un BufferedWriter en un solo try (...). Imprime errors.log y cuántas líneas escribiste.

Entrada
ERROR
Salida
ERROR: Failed to load configuration
ERROR: Null pointer exception
Escribí 2 líneas en errors.log

3. El contenido de una carpeta. Crea una carpeta project con los archivos document.txt, image.jpg, script.js y la carpeta backup. Escribe sus nombres, ordenados alfabéticamente, en directory_contents.txt, con una / después de los nombres de las carpetas. Imprime el archivo.

directory_contents.txt
backup/
document.txt
image.jpg
script.js

4. Un archivo índice. Crea text.txt con las tres líneas de abajo. Lee una palabra del teclado y escribe en index.txt cada lugar donde aparece, en la forma línea:posición: el número de línea (desde 1) y el índice dentro de la línea (desde 0). Si la palabra está dos veces en una línea, son dos entradas.

text.txt
hello world
world hello
hello again, hello
Entrada
hello
index.txt
1:0
2:6
3:0
3:13

Más tareas

  • Contador de ejecuciones. Arregla el programa de la primera tarjeta: cuenta las ejecuciones en visits.txt. Si el archivo no existe, empieza desde 0.
  • Buscar y reemplazar en un archivo. La tarea de la lección «Expresiones regulares: grupos y reemplazo»: lee un archivo, reemplaza «viejo» por «nuevo» y escribe el resultado en un archivo nuevo.
  • Fechas de un archivo. La tarea de las fechas de la lección «Expresiones regulares»: lee las fechas de un archivo, una por línea.
  • Una copia con números de línea. Lee un archivo y escribe una copia en la que cada línea empieza con su número: 1: ..., 2: ....

Test

Ponte a prueba

1. ¿Por qué el compilador pide un try/catch alrededor de Files.readString?
2. Files.writeString(file, "A\n"); y después Files.writeString(file, "B\n"); ¿Qué hay en el archivo?
3. ¿Cuándo llama try-with-resources a close()?
4. ¿Qué devuelve in.readLine() cuando el archivo se termina?
5. Escribes con un BufferedWriter, pero no lo cierras. ¿Qué puede haber en el archivo?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·