W3docs

Écrire des fichiers en Java

Écrivez des fichiers texte et binaires en Java avec FileWriter, BufferedWriter, PrintWriter et Files.writeString.

Écrire un fichier en Java consiste à convertir des données en mémoire — une String, une List de lignes ou un byte[] — en octets sur le disque. Ce chapitre couvre les cinq writers que vous utiliserez réellement, dans quelles situations chacun est adapté, les options StandardOpenOption qui déterminent le comportement d'écrasement ou d'ajout, et le bug d'écriture le plus courant : des données qui "n'ont pas été sauvegardées" parce que le writer n'a jamais été fermé.

L'écriture suit la même structure que la lecture dans le chapitre précédent — des méthodes one-liner modernes par-dessus Files, des décorateurs classiques par-dessus FileWriter, et un petit ensemble d'options qui déterminent ce qui se passe lorsque le fichier cible existe ou non.

Files.writeString(path, text) — fichier entier, un seul appel

Le pendant de Files.readString. Ajouté en Java 11.

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

Les options d'ouverture par défaut sont CREATE, WRITE, TRUNCATE_EXISTING — c'est-à-dire "créer si absent, écraser si présent". Ce comportement par défaut surprend ceux qui s'attendent à un mode ajout ; vous devez l'activer explicitement :

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

Retourne le Path que vous lui avez fourni (pratique pour le chaînage). À utiliser quand : vous avez une petite quantité de texte et souhaitez un seul appel. Même mise en garde mémoire que readString — ne construisez pas une string de 4 Go en mémoire juste pour l'écrire.

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

Deux surcharges du même Files.write :

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

La surcharge Iterable<? extends CharSequence> écrit chaque élément sur sa propre ligne avec des séparateurs \n. La surcharge byte[] écrit des octets bruts — c'est l'option idéale pour les données binaires lorsque les octets sont déjà en mémoire.

Files.newBufferedWriter(path) — la fabrique de writers moderne

Le pendant basé sur les handles et en 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();
}

À utiliser quand : vous écrivez de nombreux petits fragments (une boucle sur des enregistrements, une transformation en streaming, un writer de log) et ne souhaitez pas matérialiser tout le contenu en une string d'abord. Le buffer regroupe les écritures de sorte que l'OS voit quelques grands appels système au lieu de nombreux petits.

FileWriter et BufferedWriter — la pile classique

La version legacy "construite à la main" :

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

Trois couches, de bas en haut : FileWriter écrit des caractères bruts en utilisant le charset que vous fournissez (ou le défaut de la plateforme — ne faites jamais cela) ; BufferedWriter l'enveloppe avec un buffer en mémoire et une méthode newLine() portable. Même structure, plus de code que la forme Files.newBufferedWriter. Le nouveau code préfère la fabrique moderne ; vous verrez cette pile dans du code plus ancien.

Le deuxième argument du constructeur de FileWriter est 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

Le constructeur (String, boolean) est antérieur aux constructeurs avec gestion du charset. Mélanger les deux dans la même base de code est l'un de ces risques de maintenance liés à l'héritage — même classe, deux ordres d'arguments concurrents.

PrintWriter — sortie formatée

PrintWriter ajoute print, println et printf par-dessus n'importe quel Writer. C'est la même API que vous utilisez sur System.out (qui est lui-même un PrintStream, le pendant orienté octets).

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);
}

Deux points à retenir :

  • printf utilise %n pour le séparateur de ligne de la plateforme. \n est un LF codé en dur, ce qui est généralement ce que vous souhaitez pour les fichiers log et les données lues par des machines.
  • PrintWriter avale les IOException. print, println et printf ne lancent pas d'exception — ils définissent un indicateur d'erreur interne que vous vérifiez avec checkError(). C'est un choix délibéré pour System.out (les écritures console ne doivent pas faire planter un outil CLI), mais c'est une source de bugs pour les writers de fichiers. Si la gestion fiable des erreurs est importante, passez false au constructeur approprié et utilisez BufferedWriter pour l'écriture, PrintWriter uniquement pour les helpers de formatage — ou interrogez checkError() après les écritures.

Les options StandardOpenOption

Chaque writer moderne accepte des varargs OpenOption... qui modifient la sémantique d'ouverture :

OptionSignification
CREATECréer le fichier s'il n'existe pas ; sinon ouvrir l'existant.
CREATE_NEWCréer ; lever FileAlreadyExistsException si le fichier existe. Atomique.
TRUNCATE_EXISTINGSi le fichier existait, le vider à l'ouverture.
APPENDÉcrire à la fin du fichier sans le tronquer. Atomique sur la plupart des OS.
WRITEOuvrir en écriture. Toujours impliqué pour les writers.
SYNC / DSYNCBloquer chaque écriture jusqu'à ce que l'OS confirme qu'elle est sur le disque. Lent ; durabilité pour la sécurité en cas de crash.
DELETE_ON_CLOSESupprimer le fichier à la fermeture du flux.

Les combinaisons qui comptent :

  • Écraser (par défaut) : CREATE, TRUNCATE_EXISTING. Ce que Files.writeString et Files.newBufferedWriter utilisent par défaut.
  • Ajouter : CREATE, APPEND. Le pattern pour les fichiers log.
  • Créer ou échouer : CREATE_NEW. Le pattern pour les fichiers verrou ou "ne pas écraser".

APPEND est atomique au niveau de l'OS sur Unix : deux processus qui ajoutent au même fichier obtiennent des blocs entrelacés mais pas d'écriture partielle à l'intérieur d'un seul fragment bufférisé. C'est le contrat qui en fait le pattern standard pour la journalisation.

Le piège du "writer n'a rien écrit"

C'est le bug que rencontre chaque base de code Java une fois :

// 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 (et PrintWriter) regroupe les écritures dans un fragment en mémoire. Les octets n'atteignent pas le disque tant que le buffer n'est pas plein ou que close() n'est pas exécuté. Sans try-with-resources vous sautez la fermeture, et vos données "sauvegardées" s'évaporent.

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

Si vous avez besoin des données sur le disque avant la fermeture — par exemple, un observateur de queue doit voir chaque ligne de log — appelez flush() explicitement. Files.newBufferedWriter ne vide pas automatiquement après chaque écriture ; c'est le prix du buffer.

Quel writer utiliser

ScénarioChoisir
Petite string, en une foisFiles.writeString
Liste de lignes ou tableau d'octetsFiles.write
Streaming de nombreuses lignesFiles.newBufferedWriter
Besoin du formatage printfPrintWriter enveloppant un writer bufférisé
Code legacy uniquementBufferedWriter(new FileWriter(...))

Par défaut, utilisez Files.writeString pour "j'ai déjà le texte" et Files.newBufferedWriter pour "je vais le construire ligne par ligne". Utilisez PrintWriter uniquement quand vous avez besoin de printf.

Un exemple concret : tous les writers côte à côte

Le programme ci-dessous écrit le même contenu de trois manières différentes — one-shot moderne, streaming ligne par ligne via BufferedWriter, et formatage printf via PrintWriter — puis démontre APPEND par rapport au défaut TRUNCATE_EXISTING, et enfin le mode de défaillance "oubli de fermeture". Toutes les écritures ciblent un fichier temporaire pour que l'exemple s'exécute n'importe où.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Files.writeString et Files.write(List) sont les bons appels quand vous avez déjà tout votre contenu. Les deux ont écrasé le fichier à chaque fois car leurs options par défaut incluent TRUNCATE_EXISTING.
  • BufferedWriter et PrintWriter ont été exécutés dans des blocs try-with-resources. C'est la seule chose qui garantit que le buffer de queue atteint le disque — sautez-le et vous introduisez un bug "writer n'a rien écrit".
  • La séquence APPEND/TRUNCATE a écrit base, ajouté appended, puis tronqué et écrit truncated. Le fichier final ne contenait que truncated\n, ce qui est le piège — le mode par défaut de chaque writer moderne est d'écraser, pas d'ajouter. Vous devez l'activer explicitement.
  • CREATE_NEW sur un chemin existant a lancé FileAlreadyExistsException. C'est la sémantique "ne pas écraser" — utile pour les fichiers verrou et les marqueurs atomiques "ai-je déjà exécuté ?".
  • Le writer qui fuit avait une taille de fichier de 0 avant l'exécution de flush(). Les octets étaient en mémoire, pas sur le disque ; sans le flush() manuel (ou un close() approprié), ils auraient été perdus.

Étape suivante

Le prochain chapitre, Supprimer des fichiers en Java, conclut les chapitres sur les "opérations de fichiers de haut niveau" avec les trois méthodes de suppression : File.delete(), Files.delete() et Files.deleteIfExists() — et comment supprimer une arborescence de répertoires sans écrire la récursion à la main.

Pratique

Pratique
`Files.writeString(path, text)` sans argument `OpenOption`. Que fait-il si le fichier existe déjà ?
`Files.writeString(path, text)` sans argument `OpenOption`. Que fait-il si le fichier existe déjà ?
Was this page helpful?