W3docs

Escritura de archivos en Java

Escribe archivos de texto y binarios en Java con FileWriter, BufferedWriter, PrintWriter y Files.writeString.

Escribir un archivo en Java significa convertir datos en memoria — un String, una List de líneas o un byte[] — en bytes en disco. Este capítulo cubre los cinco escritores que realmente usarás, cuándo encaja cada uno, los indicadores de StandardOpenOption que determinan el comportamiento de sobrescritura frente a agregación, y el error de escritura más común: datos que "no se guardaron" porque el escritor nunca se cerró.

La escritura sigue la misma estructura que la lectura del capítulo anterior — instrucciones de una línea modernas sobre Files, decoradores clásicos sobre FileWriter, y un pequeño conjunto de opciones que determinan qué sucede cuando el archivo destino existe o no existe.

Files.writeString(path, text) — archivo completo en una llamada

El equivalente de Files.readString. Añadido en Java 11.

Files.writeString(Path.of("notes.txt"), "hello world\n", StandardCharsets.UTF_8);

Las opciones de apertura predeterminadas son CREATE, WRITE, TRUNCATE_EXISTING — es decir, "crear si no existe, sobrescribir si está presente". Ese comportamiento predeterminado sorprende a quienes esperan el modo de agregación; se activa explícitamente:

Files.writeString(path, "another line\n", StandardCharsets.UTF_8,
    StandardOpenOption.CREATE, StandardOpenOption.APPEND);

Devuelve el Path que le diste (práctico para encadenamiento). Úsalo cuando: tienes una pequeña cantidad de texto y quieres una sola llamada. La misma advertencia de memoria que readString — no construyas un string de 4 GB en memoria solo para escribirlo.

Files.write(path, lines) y Files.write(path, bytes)

Dos sobrecargas del mismo Files.write:

Files.write(Path.of("hosts.txt"), List.of("alpha", "beta", "gamma"), StandardCharsets.UTF_8);
Files.write(Path.of("photo.png"), pngBytes);

La sobrecarga Iterable<? extends CharSequence> escribe cada elemento en su propia línea con separadores \n. La sobrecarga byte[] escribe bytes sin procesar — tu opción preferida para datos binarios cuando los bytes ya están en memoria.

Files.newBufferedWriter(path) — la fábrica moderna de escritores

El equivalente basado en handles y orientado al streaming de Files.newBufferedReader.

try (BufferedWriter w = Files.newBufferedWriter(
        Path.of("out.txt"), StandardCharsets.UTF_8, StandardOpenOption.CREATE)) {
  w.write("first line");
  w.newLine();
  w.write("second line");
  w.newLine();
}

Úsalo cuando: estás escribiendo muchos fragmentos pequeños (un bucle sobre registros, una transformación en streaming, un escritor de logs) y no quieres materializar todo el contenido como string primero. El buffer agrupa las escrituras para que el SO vea un puñado de llamadas al sistema grandes en lugar de muchas pequeñas.

FileWriter y BufferedWriter — la pila clásica

La versión heredada de "constrúyelo tú mismo":

try (BufferedWriter w = new BufferedWriter(new FileWriter("out.txt", StandardCharsets.UTF_8))) {
  for (String line : lines) {
    w.write(line);
    w.newLine();
  }
}

Tres capas, de abajo hacia arriba: FileWriter escribe caracteres sin procesar usando el charset que proporcionas (o el predeterminado de la plataforma — nunca hagas esto); BufferedWriter lo envuelve con un buffer en memoria y un método newLine() portátil. Misma estructura, más pulsaciones de teclado que la forma con Files.newBufferedWriter. El código nuevo prefiere la fábrica moderna; verás esta pila en código antiguo.

El segundo argumento del constructor de FileWriter es append:

new FileWriter("out.txt", true);      // append mode (boolean)
new FileWriter("out.txt", StandardCharsets.UTF_8);                 // overwrite, UTF-8
new FileWriter("out.txt", StandardCharsets.UTF_8, true);            // append, UTF-8

El constructor (String, boolean) es anterior a los que soportan charset. Mezclar los dos en la misma base de código es uno de esos riesgos de mantenimiento heredado — misma clase, dos órdenes de argumentos en competencia.

PrintWriter — salida formateada

PrintWriter añade print, println y printf sobre cualquier Writer. Es la misma API que has estado usando en System.out (que es en sí mismo un PrintStream, el hermano orientado a bytes).

try (PrintWriter w = new PrintWriter(Files.newBufferedWriter(Path.of("report.txt")))) {
  w.println("Report generated");
  w.printf("user = %-10s  total = %d%n", "alice", 42);
  w.printf("user = %-10s  total = %d%n", "bob",   17);
}

Dos cosas a tener en cuenta:

  • printf usa %n para el separador de líneas de la plataforma. \n es LF fijo, que es lo que generalmente quieres para archivos de log y datos leídos por máquinas.
  • PrintWriter silencia IOException. print, println y printf no lanzan excepciones — establecen un indicador de error interno que puedes verificar con checkError(). Es una elección deliberada para System.out (las escrituras en consola no deberían hacer fallar una herramienta CLI), pero es una fuente de errores para escritores de archivos. Si el manejo confiable de errores es importante, pasa false al constructor apropiado y usa BufferedWriter para la escritura, PrintWriter solo para los helpers de formateo — o consulta checkError() después de las escrituras.

Indicadores de StandardOpenOption

Cada escritor moderno acepta varargs OpenOption... que cambian la semántica de apertura:

OpciónSignificado
CREATECrear el archivo si no existe; de lo contrario, abrir el existente.
CREATE_NEWCrear; lanzar FileAlreadyExistsException si el archivo existe. Atómico.
TRUNCATE_EXISTINGSi el archivo existía, vaciarlo al abrir.
APPENDEscribir al final del archivo sin truncar. Atómico en la mayoría de los SO.
WRITEAbrir para escritura. Siempre implícito para escritores.
SYNC / DSYNCBloquear cada escritura hasta que el SO confirme que está en disco. Lento; durabilidad para seguridad ante fallos.
DELETE_ON_CLOSEEliminar el archivo cuando el stream se cierre.

Las combinaciones que importan:

  • Sobrescribir (predeterminado): CREATE, TRUNCATE_EXISTING. Lo que usan por defecto Files.writeString y Files.newBufferedWriter.
  • Agregar: CREATE, APPEND. El patrón para archivos de log.
  • Crear o fallar: CREATE_NEW. El patrón de archivo de bloqueo o "no sobreescribir".

APPEND es atómico a nivel del SO en Unix: dos procesos que agregan al mismo archivo obtienen bloques intercalados pero sin escrituras rotas dentro de un único fragmento con buffer. Ese es el contrato que lo convierte en el patrón estándar de logging.

La trampa del "escritor no escribió nada"

Este es el error que toda base de código Java encuentra alguna vez:

// WRONG — the writer is never closed
BufferedWriter w = Files.newBufferedWriter(path);
w.write("important data");
return;       // tail buffer is still in memory; nothing reached the disk

BufferedWriter (y PrintWriter) agrupa las escrituras en un fragmento en memoria. Los bytes no llegan al disco hasta que el buffer se llena o se ejecuta close(). Sin try-with-resources te saltas el cierre, y tus datos "guardados" desaparecen.

// CORRECT
try (BufferedWriter w = Files.newBufferedWriter(path)) {
  w.write("important data");
}                          // close() runs here; tail buffer is flushed

Si necesitas datos en disco antes del cierre — por ejemplo, un observador de cola necesita ver cada línea de log — llama a flush() explícitamente. Files.newBufferedWriter no hace auto-flush después de cada escritura; ese es el precio del buffer.

Qué escritor usar

EscenarioElige
String pequeño, una sola operaciónFiles.writeString
Lista de líneas o array de bytesFiles.write
Streaming de muchas líneasFiles.newBufferedWriter
Necesitas formateo con printfPrintWriter envolviendo un escritor con buffer
Solo código heredadoBufferedWriter(new FileWriter(...))

Usa Files.writeString por defecto para "ya tengo el texto" y Files.newBufferedWriter para "lo construiré línea por línea". Recurre a PrintWriter solo cuando necesites printf.

Un ejemplo práctico: todos los escritores lado a lado

El programa siguiente escribe el mismo contenido de tres maneras diferentes — instrucción única moderna, líneas en streaming con BufferedWriter, y formateo con printf mediante PrintWriter — luego demuestra APPEND frente al TRUNCATE_EXISTING predeterminado, y finalmente el modo de fallo por "olvidé cerrar". Todas las escrituras apuntan a un archivo temporal para que el ejemplo funcione en cualquier lugar.

java— editable, runs on the server

Qué aprender de la ejecución:

  • Files.writeString y Files.write(List) son las llamadas correctas cuando ya tienes todo el contenido. Ambas sobrescribieron el archivo cada vez porque sus opciones predeterminadas incluyen TRUNCATE_EXISTING.
  • BufferedWriter y PrintWriter se ejecutaron dentro de try-with-resources. Eso es lo único que garantiza que el buffer de cola llegue al disco — omítelo y tendrás un bug de "escritor no escribió nada".
  • La secuencia APPEND/TRUNCATE escribió base, agregó appended, luego truncó y escribió truncated. El archivo final contenía solo truncated\n, que es la trampa — el modo predeterminado de todos los escritores modernos es sobrescribir, no agregar. Hay que activarlo explícitamente.
  • CREATE_NEW sobre una ruta existente lanzó FileAlreadyExistsException. Esa es la semántica de "no sobreescribir" — útil para archivos de bloqueo y marcadores atómicos de "¿ya se ha ejecutado?".
  • El escritor con fuga tenía un tamaño de archivo de 0 antes de que se ejecutara flush(). Los bytes estaban en memoria, no en disco; sin el flush() manual (o un close() apropiado), se habrían perdido.

Qué sigue

El siguiente capítulo, Eliminación de archivos en Java, cierra los capítulos de "operaciones de archivos de alto nivel" con los tres eliminadores: File.delete(), Files.delete() y Files.deleteIfExists() — y cómo eliminar un árbol de directorios sin escribir la recursión a mano.

Práctica

Práctica
`Files.writeString(path, text)` sin argumentos `OpenOption`. ¿Qué hace si el archivo ya existe?
`Files.writeString(path, text)` sin argumentos `OpenOption`. ¿Qué hace si el archivo ya existe?
Was this page helpful?