W3docs

Escrevendo Arquivos em Java

Escreva arquivos de texto e binários em Java com FileWriter, BufferedWriter, PrintWriter e Files.writeString.

Escrever um arquivo em Java significa transformar dados na memória — uma String, uma List de linhas ou um byte[] — em bytes no disco. Este capítulo aborda os cinco writers que você realmente vai usar, quando cada um se encaixa, as flags de StandardOpenOption que decidem o comportamento de sobrescrever versus anexar, e o bug de escrita mais comum: dados que "não foram salvos" porque o writer nunca foi fechado.

A escrita segue a mesma estrutura que a leitura do capítulo anterior — one-liners modernos em cima de Files, decoradores clássicos em cima de FileWriter, e um pequeno conjunto de opções que decide o que acontece quando o arquivo de destino existe ou não.

Files.writeString(path, text) — arquivo inteiro em uma chamada

O par de Files.readString. Adicionado no Java 11.

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

As opções de abertura padrão são CREATE, WRITE, TRUNCATE_EXISTING — ou seja, "criar se não existir, sobrescrever se existir." Esse padrão pega desprevenidos quem espera comportamento de append; você opta por ele explicitamente:

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

Retorna o Path que você forneceu (útil para encadeamento). Use quando: você tem uma pequena quantidade de texto e quer uma única chamada. A mesma ressalva de memória que readString — não construa uma string de 4 GB na memória só para escrevê-la.

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

Duas sobrecargas do mesmo Files.write:

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

A sobrecarga Iterable<? extends CharSequence> escreve cada elemento em sua própria linha com separadores \n. A sobrecarga byte[] escreve bytes brutos — sua escolha para dados binários quando os bytes já estão na memória.

Files.newBufferedWriter(path) — a factory de writer moderna

O equivalente baseado em handle e 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();
}

Use quando: você está escrevendo muitos pequenos pedaços (um loop sobre registros, uma transformação em streaming, um writer de log) e não quer materializar todo o conteúdo como string primeiro. O buffer agrupa escritas para que o SO veja um pequeno número de syscalls grandes em vez de muitas pequenas.

FileWriter e BufferedWriter — a pilha clássica

A versão legada "monte você mesmo":

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

Três camadas, de baixo para cima: FileWriter escreve caracteres brutos usando o charset que você fornece (ou o padrão da plataforma — nunca faça isso); BufferedWriter envolve com um buffer em memória e um método newLine() portátil. Mesma estrutura, mais digitação do que a forma Files.newBufferedWriter. Código novo prefere a factory moderna; você vai encontrar essa pilha em código mais antigo.

O segundo argumento do construtor de FileWriter é 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

O construtor (String, boolean) é anterior aos construtores com suporte a charset. Misturar os dois na mesma base de código é um daqueles riscos de manutenção legada — mesma classe, duas ordens de argumentos concorrentes.

PrintWriter — saída formatada

PrintWriter adiciona print, println e printf sobre qualquer Writer. É a mesma API que você usa em System.out (que é um PrintStream, o irmão 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);
}

Duas coisas importantes:

  • printf usa %n para o separador de linha da plataforma. \n é LF fixo, que é o que você geralmente quer para arquivos de log e dados lidos por máquina.
  • PrintWriter suprime IOException. print, println e printf não lançam exceções — eles definem uma flag de erro interna que você verifica com checkError(). Essa é uma escolha deliberada para System.out (escritas no console não devem derrubar uma ferramenta CLI), mas é um imã de bugs para writers de arquivo. Se o tratamento confiável de erros é importante, passe false ao construtor apropriado e use BufferedWriter para a escrita, PrintWriter apenas para os auxiliares de formatação — ou consulte checkError() após as escritas.

Flags de StandardOpenOption

Todo writer moderno aceita varargs OpenOption... que alteram a semântica de abertura:

OpçãoSignificado
CREATECria o arquivo se não existir; caso contrário abre o existente.
CREATE_NEWCria; lança FileAlreadyExistsException se o arquivo existir. Atômico.
TRUNCATE_EXISTINGSe o arquivo existia, limpa-o na abertura.
APPENDEscreve no final do arquivo sem truncar. Atômico na maioria dos SOs.
WRITEAbre para escrita. Sempre implícito para writers.
SYNC / DSYNCBloqueia cada escrita até o SO reportar que está no disco. Lento; durabilidade para segurança em caso de crash.
DELETE_ON_CLOSEExclui o arquivo quando o stream é fechado.

As combinações que importam:

  • Sobrescrever (padrão): CREATE, TRUNCATE_EXISTING. O que Files.writeString e Files.newBufferedWriter usam por padrão.
  • Anexar: CREATE, APPEND. O padrão para arquivos de log.
  • Criar ou falhar: CREATE_NEW. O padrão para arquivo de lock ou "não clobber".

APPEND é atômico no SO em Unix: dois processos anexando ao mesmo arquivo obtêm blocos intercalados, mas sem escritas corrompidas dentro de um único pedaço bufferizado. Esse é o contrato que o torna o padrão de logging padrão.

A armadilha do "writer não escreveu nada"

Este é o bug que toda base de código Java encontra uma 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 (e PrintWriter) agrupa escritas em um pedaço na memória. Os bytes não chegam ao disco até que o buffer encha ou close() seja executado. Sem try-with-resources você pula o close, e seus dados "salvos" evaporam.

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

Se você precisa dos dados no disco antes do close — digamos, um tail-watcher precisa ver cada linha de log — chame flush() explicitamente. Files.newBufferedWriter não faz auto-flush após cada escrita; esse é o preço do buffer.

Qual writer usar

CenárioEscolha
String pequena, uma chamadaFiles.writeString
Lista de linhas ou array de bytesFiles.write
Streaming de muitas linhasFiles.newBufferedWriter
Precisa de formatação printfPrintWriter envolvendo um writer bufferizado
Apenas código legadoBufferedWriter(new FileWriter(...))

Use Files.writeString como padrão para "já tenho o texto" e Files.newBufferedWriter para "vou construir linha a linha." Use PrintWriter apenas quando precisar de printf.

Um exemplo completo: todos os writers lado a lado

O programa abaixo escreve o mesmo conteúdo de três maneiras diferentes — one-shot moderno, streaming linha a linha via BufferedWriter e formatado com printf via PrintWriter — depois demonstra APPEND versus o padrão TRUNCATE_EXISTING e, por fim, o modo de falha do "esqueceu de fechar". Todas as escritas têm como alvo um arquivo temporário para que o exemplo rode em qualquer lugar.

java— editable, runs on the server

O que tirar da execução:

  • Files.writeString e Files.write(List) são as chamadas corretas quando você já tem todo o conteúdo. Ambos sobrescreveram o arquivo a cada vez porque suas opções padrão incluem TRUNCATE_EXISTING.
  • BufferedWriter e PrintWriter foram executados dentro de try-with-resources. Essa é a única coisa que garante que o buffer final chegue ao disco — pule isso e você entrega um bug de "writer não escreveu nada".
  • A sequência APPEND/TRUNCATE escreveu base, anexou appended, depois truncou e escreveu truncated. O arquivo final continha apenas truncated\n, que é a armadilha — o modo padrão de todo writer moderno é sobrescrever, não anexar. Você tem que optar por isso.
  • CREATE_NEW em um caminho existente lançou FileAlreadyExistsException. Essa é a semântica de "não clobber" — útil para arquivos de lock e marcadores atômicos de "já rodei antes?".
  • O writer com vazamento tinha tamanho de arquivo 0 antes de flush() ser chamado. Os bytes estavam na memória, não no disco; sem o flush() manual (ou um close() adequado), eles teriam sido perdidos.

O que vem a seguir

O próximo capítulo, Excluindo Arquivos em Java, encerra os capítulos de "operações de arquivo de alto nível" com os três deleters: File.delete(), Files.delete() e Files.deleteIfExists() — e como remover uma árvore de diretórios sem escrever a recursão manualmente.

Prática

Prática
`Files.writeString(path, text)` sem argumentos `OpenOption`. O que ele faz se o arquivo já existir?
`Files.writeString(path, text)` sem argumentos `OpenOption`. O que ele faz se o arquivo já existir?
Was this page helpful?