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.");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:
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()); } }}Comprar lechePagar la luz antes del 15Pathes 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.Filestiene métodos estáticos para todo lo que tiene que ver con archivos.writeStringescribe texto (y crea el archivo si no existe),readStringlo lee entero.IOExceptiones 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());}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
APPENDpor sí solo no crea el archivo. Si no existe, obtienesNoSuchFileException. 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). readAllLinesyreadStringleen todo el archivo en la memoria. Para un archivo de varios gigabytes es mejor leer línea por línea. Mira la tarjeta sobreBufferedReader.
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());}writeString sin APPEND borra el contenido anterior y escribe el nuevo. La primera línea ya no está, incluso antes de la lectura. Es la razón más común de «se me borró el archivo».
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:
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()); }}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")));No hay archivo config.txt. Uso la configuración por defecto.falseFiles.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());}Día 1: 120 €Día 2: 240 €Día 3: 360 €writeescribe texto,newLine()pasa a una línea nueva.readLine()devuelve la siguiente línea, onullcuando 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
- Path es solo la dirección del archivo. A la izquierda está la memoria del programa, a la derecha el disco.
- newBufferedWriter crea el archivo vacío report.txt y un objeto con un búfer en la memoria.
- write y newLine escriben en el búfer. Al disco todavía no llega nada.
- El archivo en el disco todavía tiene 0 bytes.
- El bloque termina y try llama a close(). El búfer va al disco: el archivo pasa a tener 48 bytes.
- 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.
- 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.
BufferedWriterreú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,BufferedReaderlee mucho de una vez. - Bytes y caracteres. Por debajo, un archivo es una secuencia de bytes.
InputStreamyOutputStream(por ejemploFiles.newInputStream) leen y escriben bytes: para fotos, archivos comprimidos, todo lo que no es texto.ReaderyWriterconvierten los bytes en caracteres según la codificación.BufferedReaderes unReadercon búfer. - La forma antigua. En el código de otras personas verás
new BufferedReader(new FileReader("report.txt"))ynew Scanner(new File("report.txt")). Funcionan, peroFileses más nuevo y más cómodo.Scannertambién lee de un archivo, con loshasNextLineynextLineque 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.");}parseInt("tres") lanza una excepción, y la línea «Cantidad» se salta. Al salir de try, el recurso se cierra enseguida, incluso antes de catch. Por eso «libre» aparece antes de «Cantidad incorrecta.» Cuando catch se ejecuta, la impresora ya está liberada.Pruébalo
Encuentra el error
0. ¿En qué línea está el error?Elige la línea con el error.
BufferedWriter nunca se cierra. El texto sigue en el búfer en la memoria y no ha llegado al archivo, así que el archivo está vacío. El programa no da ningún error: pierde datos sin avisar. Ábrelo en try (BufferedWriter out = ...) { ... } y lee el archivo después del bloque: entonces está cerrado y la línea está ahí.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());}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.deletelanzaDirectoryNotEmptyExceptionsi la carpeta no está vacía, yNoSuchFileExceptionsi no existe.Files.deleteIfExistsno se queja si no existe.Files.list(dir)es la forma nueva de obtener el contenido de una carpeta, pero devuelve unStream, que está en el módulo «Java moderno».Files.copyyFiles.movecopian y mueven.- En código más antiguo verás la clase
java.io.File:new File("notes.txt")con los métodosexists(),length(),delete(),renameTo(...),createNewFile()ygetAbsolutePath(). Existía antes quePathyFiles, ypath.toFile()yfile.toPath()convierten uno en otro. Allí, añadir al final esnew 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 =.
# configuración del servidormax_connections=500log_file_path=/var/log/server.log
db_timeout=30max_connections: 500log_file_path: /var/log/server.logdb_timeout: 302. 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.
ERRORERROR: Failed to load configurationERROR: Null pointer exceptionEscribí 2 líneas en errors.log3. 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.
backup/document.txtimage.jpgscript.js4. 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.
hello worldworld hellohello again, hellohello1:02:63:03:13Má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
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Interfaces · Excepciones · try, catch y finally · Cadenas
- Continúa en: Datos en archivos · Códigos hash y funciones hash · Programación en red · Bases de datos
- Ejercicios sobre el tema: Texto y archivos