1. Escritura con sobrescritura
Cuando trabajas con archivos, es importante entender que «escribir en un archivo» no siempre significa lo mismo. A veces necesitas completamente sobrescribir el archivo (por ejemplo, cuando guardas un informe nuevo), y otras — añadir nueva información al final de un archivo ya existente (por ejemplo, llevar el log de eventos de una aplicación). En algunos casos solo necesitas leer el contenido del archivo sin modificarlo.
En Java (especialmente en el paquete moderno java.nio.file) el modo de trabajo con un archivo se determina mediante un conjunto de opciones especiales que pasas a los métodos de escritura, por ejemplo, a Files.write(). Estas opciones te permiten indicar explícitamente si quieres sobrescribir el archivo o añadir algo al final.
Cómo funciona
Cuando llamas a Files.write(path, data), Java por defecto crea un archivo nuevo o sobrescribe el ya existente. Todo el contenido anterior del archivo se elimina y en su lugar quedan solo los datos nuevos.
Este comportamiento por defecto es una especie de «reinicio forzoso» del archivo. Si antes el archivo tenía 1000 líneas y escribes una sola, todo lo anterior desaparecerá sin dejar rastro.
Ejemplo: escribir una cadena en un archivo
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class OverwriteFileExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
List<String> lines = List.of("¡Hola, mundo!", "Este es un nuevo registro en el archivo.");
// Escribimos las líneas en el archivo (el contenido antiguo se eliminará)
Files.write(path, lines);
System.out.println("¡El archivo se ha sobrescrito correctamente!");
}
}
Después de ejecutar este código, en el archivo myfile.txt solo quedarán las dos líneas de la lista lines. Todo lo que hubiera antes desaparecerá (sin ningún «remordimiento» por parte de Java).
2. Anexado (append): añadir datos al final del archivo
En algunas tareas (por ejemplo, llevar un registro de eventos, logging, acumulación de datos) se requiere no destruir el contenido antiguo del archivo, sino añadir nuevas líneas al final. En la API antigua se usaba a menudo FileWriter con el flag append = true. En la API moderna todo se hace de forma más simple y explícita.
Cómo se hace
Todo lo que necesitas es pasar la opción StandardOpenOption.APPEND al método Files.write():
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class AppendFileExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
List<String> moreLines = List.of("Hemos añadido otra línea más.", "¡Y otra más!");
// Añadimos líneas al final del archivo (el contenido antiguo se mantiene)
Files.write(path, moreLines, StandardOpenOption.APPEND);
System.out.println("¡Las líneas se han añadido al final del archivo!");
}
}
Punto importante: si el archivo no existe, al intentar anexar se producirá un error — Java no creará el archivo automáticamente en modo APPEND. Para crearlo si no existe, utiliza dos opciones a la vez:
Files.write(path, moreLines, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
Cómo se ve en la práctica
- Ejecutas el programa con escritura (sobrescritura): en el archivo hay dos líneas.
- Lo ejecutas con anexado: a esas líneas se añaden otras nuevas; las antiguas permanecen.
3. Combinación de opciones: CREATE, APPEND, TRUNCATE_EXISTING
En Java puedes combinar varias opciones para un control más flexible del modo de apertura del archivo:
- StandardOpenOption.CREATE — crear el archivo si no existe.
- StandardOpenOption.APPEND — añadir datos al final.
- StandardOpenOption.TRUNCATE_EXISTING — truncar el archivo a cero bytes (limpiarlo) si existe.
- StandardOpenOption.CREATE_NEW — crear un archivo nuevo; si ya existe — error.
Ejemplo: crear el archivo si no existe y añadir datos al final
Files.write(path, moreLines, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
Ejemplo: crear el archivo o limpiarlo por completo y escribir datos nuevos
Files.write(path, lines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
Tabla: modos de escritura principales
| Opción(es) | Comportamiento |
|---|---|
| (por defecto) | Crear el archivo o sobrescribir el existente |
|
Añadir al final; error si el archivo no existe |
|
Añadir al final; crear el archivo si no existe |
|
Truncar el archivo a cero y escribir datos nuevos |
|
Crear un archivo nuevo; error si el archivo ya existe |
4. Lectura y escritura de archivos binarios
Hasta ahora hemos trabajado con cadenas. Pero a veces hace falta escribir o leer bytes «en bruto» (por ejemplo, imágenes, archivos comprimidos, archivos PDF). En ese caso usa:
- Files.readAllBytes(path) — lee el archivo en un array de bytes.
- Files.write(path, byteArray) — escribe un array de bytes en el archivo.
Ejemplo: copiar un archivo
import java.nio.file.*;
import java.io.IOException;
public class CopyBinaryFileExample {
public static void main(String[] args) throws IOException {
Path source = Paths.get("logo.png");
Path target = Paths.get("logo_copy.png");
byte[] data = Files.readAllBytes(source);
Files.write(target, data);
System.out.println("¡Archivo copiado!");
}
}
Ejemplo: anexado de datos binarios
byte[] moreData = new byte[] {1, 2, 3, 4, 5};
Files.write(path, moreData, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
Atención: el anexado de datos binarios a un archivo que ya contiene datos estructurados (por ejemplo, una imagen) normalmente lo estropeará. Usa el anexado solo para archivos de texto o binarios especialmente preparados (por ejemplo, logs).
5. Cuándo hacen falta los flujos (FileInputStream, BufferedReader y otros)
Los métodos Files.readAllBytes(), Files.write() son cómodos para archivos pequeños y medianos, cuando se puede cargar todo el contenido en memoria de una vez. Si el archivo es grande (por ejemplo, gigabytes), o si quieres leerlo línea a línea (por ejemplo, para analizar logs), usa flujos.
Ejemplo: lectura del archivo línea a línea con BufferedReader
import java.nio.file.*;
import java.io.*;
public class ReadLinesExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
try (BufferedReader reader = Files.newBufferedReader(path)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Línea: " + line);
}
}
}
}
Ejemplo: escritura del archivo línea a línea con BufferedWriter y anexado
import java.nio.file.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class WriteAppendExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("myfile.txt");
try (BufferedWriter writer = Files.newBufferedWriter(
path,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND)) {
writer.write("¡Otra línea más con BufferedWriter!");
writer.newLine();
}
}
}
6. Práctica: creamos un archivo y le vamos anexando líneas
Vamos a unirlo todo en una única aplicación. Supongamos que tenemos un programa que mantiene una lista de tareas (todo-list) en un archivo de texto. Cada vez que el usuario añade una tarea nueva, la anexamos al final del archivo.
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
public class TodoList {
public static void main(String[] args) throws IOException {
Path path = Paths.get("todo.txt");
Scanner scanner = new Scanner(System.in);
System.out.print("Introduzca una nueva tarea: ");
String task = scanner.nextLine();
// Anexamos la tarea al final del archivo (creamos el archivo si no existe)
Files.write(path,
List.of(task),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
System.out.println("¡Tarea añadida!");
}
}
Ejecuta el programa varias veces — cada tarea aparecerá en el archivo en una nueva línea. ¡Y ya tenemos nuestra primera lista de tareas «perenne»!
7. Gestión de errores: ¿qué puede salir mal?
Trabajar con archivos siempre conlleva riesgo de errores. Esto es lo que puede ocurrir:
- IOException — la excepción base para todos los errores de entrada/salida. Puede producirse si el archivo está ocupado por otro programa, si no hay permisos de escritura/lectura, si el disco está lleno, etc.
- NoSuchFileException — si intentas leer o anexar a un archivo que no existe (y no has indicado la opción CREATE).
- FileAlreadyExistsException — si usas la opción CREATE_NEW y el archivo ya existe.
Recomendación: envuelve siempre el trabajo con archivos en try-catch:
try {
// sus operaciones con archivos
} catch (IOException e) {
System.out.println("Error al trabajar con el archivo: " + e.getMessage());
}
8. Errores típicos al trabajar con los modos de apertura de archivos
Error nº 1: olvidaste indicar la opción CREATE al anexar. Si intentas anexar (APPEND) a un archivo que aún no existe, obtendrás NoSuchFileException. Añade siempre CREATE si quieres que el archivo se cree automáticamente.
Error nº 2: sobrescritura accidental del archivo. Una llamada a Files.write(path, data) sin opciones adicionales elimina todo el contenido antiguo del archivo. Si quieres añadir datos, y no eliminar los antiguos, usa APPEND.
Error nº 3: intentar anexar datos binarios a un archivo de texto (o al revés). Si mezclas datos de texto y binarios en un mismo archivo, probablemente obtendrás un archivo ilegible. Mantén siempre un formato por archivo.
Error nº 4: olvidaste cerrar el flujo. Si usas flujos (BufferedWriter, BufferedReader), no olvides cerrarlos (mejor con try-with-resources), de lo contrario el archivo puede quedar bloqueado.
Error nº 5: no gestionaste las excepciones. Cualquier operación con archivos puede lanzar IOException. Si no gestionas el error, el programa terminará con un fallo.
GO TO FULL VERSION