W3docs

Excluindo Arquivos em Java

Exclua arquivos e diretórios em Java com File.delete, Files.delete e Files.deleteIfExists.

Três chamadas simples, uma grande diferença. File.delete() retorna um boolean para tudo, desde sucesso até permissão negada; Files.delete() lança exceções específicas para cada falha; Files.deleteIfExists() é a opção intermediária — boolean para a pergunta "eu deletei algo?" apenas, exceções para falhas reais. Escolher a certa depende principalmente de quanto você se importa com por que a exclusão falhou.

Também abordado: remover uma árvore de diretório não vazia (nenhuma das três chamadas faz isso sozinha), a opção de abertura DELETE_ON_CLOSE para arquivos temporários, e o padrão seguro de "mover e depois excluir" para substituir um arquivo atomicamente.

File.delete() — legado, retorna boolean

File f = new File("notes.txt");
boolean ok = f.delete();           // true on success
                                   // false on every failure: missing, locked, perms, non-empty dir

A lacuna do legado, mesma forma que mkdir e renameTo: um único boolean para cada resultado. A chamada retorna false se o arquivo não existia, se você não tinha permissão, se o caminho era um diretório não vazio ou — no Windows — se outro processo mantinha o arquivo aberto. Você não consegue distinguir qual situação ocorreu pelo valor de retorno.

File.delete() remove:

  • Um arquivo regular.
  • Um diretório vazio. Diretórios não vazios retornam false.
  • Um link simbólico em si (não o destino).

Se você precisa apenas de "o arquivo não existe mais ao final da chamada," verifique f.exists() depois em vez de depender do retorno:

f.delete();
if (f.exists()) throw new IOException("could not delete " + f);

Esse padrão é o que você encontrará substituindo o método na maioria das bases de código mais antigas.

Files.delete(path) — moderno, lança exceções

O equivalente em java.nio.file troca o boolean por exceções específicas:

Path p = Path.of("notes.txt");
Files.delete(p);
// throws NoSuchFileException        — the file didn't exist
// throws DirectoryNotEmptyException — path was a non-empty directory
// throws AccessDeniedException      — permission denied
// throws IOException                — anything else (locked, OS error, network FS hiccup)

Os tipos de exceção são subclasses de IOException, então um catch (IOException e) amplo ainda funciona. A diferença é que o tratamento de erros real pode ser específico:

try {
  Files.delete(path);
} catch (NoSuchFileException e) {
  // already gone — not an error in the "delete if there" pattern
} catch (DirectoryNotEmptyException e) {
  // need a recursive delete; handled below
}

Se "já foi embora" está tudo bem, use Files.deleteIfExists em vez de capturar NoSuchFileException.

Files.deleteIfExists(path) — a opção intermediária

boolean deleted = Files.deleteIfExists(path);
// returns true  — the file existed and was deleted
// returns false — the file did not exist
// throws DirectoryNotEmptyException, AccessDeniedException, etc. for real failures

O boolean aqui apenas distingue "eu removi algo" de "nada para remover." Erros reais ainda lançam exceções. É a chamada que você quer para código de setUp / tearDown, limpeza idempotente e padrões de "excluir este marcador antigo se estiver lá":

Files.deleteIfExists(Path.of("lock"));     // safe whether the lock was there or not

Use o quadro:

Você quer…Use
Boolean legado, sem informação de erroFile.delete()
"Excluir ou me diga por que falhou"Files.delete(path)
"Excluir se existir; silencioso se não"Files.deleteIfExists(path)

Excluindo uma árvore de diretório não vazia

Nenhuma das chamadas de exclusão única remove um diretório não vazio. Existem dois padrões padrão:

Padrão 1: percorrer e excluir em ordem reversa. Files.walk produz um Stream<Path> em ordem arbitrária; ordenar em ordem reversa empurra as folhas para frente, de modo que cada pai está vazio quando você chega a ele.

try (Stream<Path> walk = Files.walk(root)) {
  walk.sorted(Comparator.reverseOrder())
      .forEach(p -> {
        try { Files.delete(p); } catch (IOException e) { throw new UncheckedIOException(e); }
      });
}

Conciso e a versão que a maioria do código usa hoje. A desvantagem: ele carrega todos os caminhos na ordenação. Para diretórios com milhões de entradas, prefira o padrão 2.

Padrão 2: Files.walkFileTree com um SimpleFileVisitor. O padrão visitor permite que você exclua folhas na visita e pais em postVisitDirectory, sem necessidade de ordenação:

Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
  @Override public FileVisitResult visitFile(Path file, BasicFileAttributes a) throws IOException {
    Files.delete(file);
    return FileVisitResult.CONTINUE;
  }
  @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
    Files.delete(dir);
    return FileVisitResult.CONTINUE;
  }
});

Mesmo resultado, sem ordenação em memória, mais linhas. A API visitor é abordada no capítulo Walk file tree.

Não existe rmrf embutido no JDK. Ambos os padrões acima são os substitutos padrão; muitas bases de código incluem um pequeno helper Files.deleteRecursively(root) em cima de um deles.

DELETE_ON_CLOSE — arquivos temporários que se limpam sozinhos

Para "preciso de um arquivo apenas enquanto este stream estiver aberto," a opção StandardOpenOption.DELETE_ON_CLOSE exclui o arquivo quando o stream fecha — mesmo no caminho de exceção:

Path scratch = Files.createTempFile("work-", ".tmp");
try (BufferedWriter w = Files.newBufferedWriter(scratch,
        StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) {
  // ... write to and read from scratch ...
}      // scratch is gone after this brace, regardless of how we got here

O arquivo é desvinculado do diretório imediatamente na maioria dos sistemas Unix (outros processos não podem mais vê-lo pelo nome; apenas o handle o mantém vivo). Esse é o padrão certo para dados temporários de curta duração — sem necessidade de lembrar o encanamento de try/finally.

File.deleteOnExit() é a versão mais antiga e mais fraca: ela enfileira uma exclusão para ser executada durante o encerramento da JVM. Não é chamada em kill -9 ou em uma falha da JVM, portanto vaza. Use DELETE_ON_CLOSE quando o tempo de vida do arquivo estiver vinculado a um stream; use um try/finally (ou try-with-resources em torno de um holder AutoCloseable) quando não estiver.

Uma nota sobre "substituição atômica"

Substituir um arquivo por outro atomicamente — de modo que um leitor nunca veja uma versão meio escrita — não é um padrão de excluir-e-escrever. O idioma padrão é "escrever em um arquivo irmão, depois renomear atomicamente":

Path target = Path.of("data.json");
Path tmp    = target.resolveSibling("data.json.tmp");
Files.writeString(tmp, payload);
Files.move(tmp, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);

ATOMIC_MOVE troca os dois caminhos em uma única etapa do SO (em sistemas de arquivos que suportam isso). O data.json antigo é substituído; não existe nenhum momento intermediário onde o arquivo está meio escrito ou ausente.

Um exemplo prático: cada método de exclusão e uma desmontagem recursiva

O programa abaixo constrói uma pequena árvore e, em seguida, exercita cada método de exclusão por vez — o boolean legado, a versão moderna que lança exceções, o intermediário "se existir" e, finalmente, o padrão Files.walk + reverseOrder que remove uma árvore inteira. Cada etapa imprime o que aconteceu.

java— editable, runs on the server

O que tirar da execução:

  • As duas chamadas File.delete() em a.txt retornaram true e depois false. O segundo false parece idêntico a uma falha de permissão — essa é a lacuna do legado.
  • Files.delete(sub) lançou DirectoryNotEmptyException e Files.delete(missing) lançou NoSuchFileException. Duas subclasses específicas de IOException, dois modos de falha distintos — exatamente o que a API boolean não consegue informar.
  • Files.deleteIfExists(b) retornou true na primeira vez e false na segunda. Esse segundo false é apenas "não estava lá" — uma falha real (permissão negada, bloqueio) teria lançado uma exceção.
  • O bloco Files.walk + reverseOrder excluiu as folhas primeiro e os pais por último. Cada chamada Files.delete ao longo do caminho foi bem-sucedida porque, quando o visitor chegou a um diretório, seus filhos já haviam sido removidos.
  • O arquivo DELETE_ON_CLOSE havia desaparecido quando o writer fechou — garantido mesmo no caminho de exceção. (Na maioria dos sistemas Unix ele é desvinculado do diretório imediatamente na abertura, então Files.exists pode já reportar false dentro do try; no Windows ele sobrevive até o handle fechar. De qualquer forma, nada é deixado para trás depois.) Esse é o padrão de arquivo temporário mais limpo no JDK — sem shutdown hook, sem try/finally para lembrar.

O que vem a seguir

Isso encerra os capítulos de alto nível sobre "fazer uma coisa a um arquivo". O próximo capítulo, Byte Streams em Java, desce uma camada: InputStream e OutputStream, a abstração bruta orientada a bytes sobre a qual todos os arquivos, sockets e pipes em java.io são construídos. Muitos dos helpers que você usou até agora — Files.readString, Files.newBufferedWriter, até FileReader — são decoradores sobre essas duas interfaces.

Prática

Prática
`Files.deleteIfExists(path)` retorna `false` quando…
`Files.deleteIfExists(path)` retorna `false` quando…
Was this page helpful?