Como criar um diretório / pasta temporário em Java?

Respostas:

390

Se você estiver usando o JDK 7, use a nova classe Files.createTempDirectory para criar o diretório temporário.

Path tempDirWithPrefix = Files.createTempDirectory(prefix);

Antes do JDK 7, isso deveria ser feito:

public static File createTempDirectory()
    throws IOException
{
    final File temp;

    temp = File.createTempFile("temp", Long.toString(System.nanoTime()));

    if(!(temp.delete()))
    {
        throw new IOException("Could not delete temp file: " + temp.getAbsolutePath());
    }

    if(!(temp.mkdir()))
    {
        throw new IOException("Could not create temp directory: " + temp.getAbsolutePath());
    }

    return (temp);
}

Você pode criar exceções melhores (subclasse IOException), se desejar.

TofuBeer
fonte
12
Isso é perigoso. Java não é conhecida a excluir arquivos imediatamente, assim mkdir pode falhar algumas vezes
Demiurg
4
@ Demiurg O único caso de um arquivo não ser excluído imediatamente é no Windows quando o arquivo já está aberto (pode ser aberto por um antivírus, por exemplo). Você tem outra documentação para mostrar o contrário (estou curioso sobre coisas assim :-)? Se isso acontecer regularmente, o código acima não funcionará; se for raro, interromper a chamada para o código acima até que a exclusão ocorra (ou algumas tentativas máximas sejam atingidas) funcionem.
TofuBeer 01/12/2010
6
O @Demiurg Java é conhecido por não excluir arquivos imediatamente. Isso é verdade, mesmo se você não abrir. Então, é uma maneira mais segura temp.delete(); temp = new File(temp.getPath + ".d"); temp.mkdir(); ..., temp.delete();.
Xiè Jìléi
102
Esse código tem uma condição de corrida entre delete()e mkdir(): Enquanto isso, um processo malicioso pode criar o diretório de destino (usando o nome do arquivo criado recentemente). Veja Files.createTempDir()uma alternativa.
Joachim Sauer
11
Eu gosto de ! para se destacar, muito fácil de perder. Eu li um monte de código escrito por estudantes ... se é suficiente comum a ser irritante :-) (i!)
TofuBeer
182

A biblioteca do Google Guava possui vários utilitários úteis. Uma nota aqui é a classe Arquivos . Possui vários métodos úteis, incluindo:

File myTempDir = Files.createTempDir();

Isso faz exatamente o que você pediu em uma linha. Se você ler a documentação aqui, verá que a adaptação proposta File.createTempFile("install", "dir")geralmente apresenta vulnerabilidades de segurança.

Spina
fonte
Gostaria de saber a que vulnerabilidade você se refere. Essa abordagem não parece criar uma condição de corrida, pois o arquivo.mkdir () deve falhar se esse diretório já existir (criado por um invasor). Também não acho que essa ligação siga os links simbólicos maliciosos. Você poderia esclarecer o que você quis dizer?
abb 27/07
3
@abb: Não sei os detalhes da condição de corrida mencionados na documentação do Guava. Suspeito que a documentação esteja correta, uma vez que indica especificamente o problema.
Spina
11
@abb Você está certo. Enquanto o retorno de mkdir () for verificado, será seguro. O código que Spina aponta para usa esse método mkdir (). grepcode.com/file/repo1.maven.org/maven2/com.google.guava/guava/… . Esse é apenas um problema em potencial nos sistemas Unix ao usar o diretório / tmp porque ele possui o bit adesivo ativado.
21812 Sarel Botha
@SarelBotha obrigado por preencher o espaço em branco aqui. Eu estava me perguntando sobre isso há algum tempo.
Spina
168

Se você precisa de um diretório temporário para testar e você estiver usando JUnit, @Rulejuntamente com TemporaryFolderresolve o seu problema:

@Rule
public TemporaryFolder folder = new TemporaryFolder();

A partir da documentação :

A regra TemporaryFolder permite a criação de arquivos e pastas que são garantidos para serem excluídos quando o método de teste terminar (se passa ou falha)


Atualizar:

Se você estiver usando o JUnit Jupiter (versão 5.1.1 ou superior), terá a opção de usar o JUnit Pioneer, que é o JUnit 5 Extension Pack.

Copiado da documentação do projeto :

Por exemplo, o teste a seguir registra a extensão para um único método de teste, cria e grava um arquivo no diretório temporário e verifica seu conteúdo.

@Test
@ExtendWith(TempDirectory.class)
void test(@TempDir Path tempDir) {
    Path file = tempDir.resolve("test.txt");
    writeFile(file);
    assertExpectedFileContent(file);
}

Mais informações no JavaDoc e no JavaDoc do TempDirectory

Gradle:

dependencies {
    testImplementation 'org.junit-pioneer:junit-pioneer:0.1.2'
}

Maven:

<dependency>
   <groupId>org.junit-pioneer</groupId>
   <artifactId>junit-pioneer</artifactId>
   <version>0.1.2</version>
   <scope>test</scope>
</dependency>

Atualização 2:

A anotação @TempDir foi adicionada à versão do JUnit Jupiter 5.4.0 como um recurso experimental. Exemplo copiado do Guia do Usuário JUnit 5 :

@Test
void writeItemsToFile(@TempDir Path tempDir) throws IOException {
    Path file = tempDir.resolve("test.txt");

    new ListWriter(file).write("a", "b", "c");

    assertEquals(singletonList("a,b,c"), Files.readAllLines(file));
}
Matsev
fonte
8
Disponível desde JUnit 4.7
Eduard Wirch
Não funciona no JUnit 4.8.2 no Windows 7! (Problema de permissões)
exceção
2
@ CraigRinger: Por que não é sensato confiar nisso?
Adam Parkin
2
@AdamParkin Honestamente, não me lembro mais. Explicação falhou!
Craig Ringer
11
O principal benefício dessa abordagem é que o diretório é gerenciado pelo JUnit (criado antes do teste e excluído recursivamente após o teste). E isso funciona. Se você receber "temp dir ainda não criado", pode ser porque você esqueceu @Rule ou o campo não é público.
Bogdan Calmac
42

O código escrito de forma ingênua para resolver esse problema sofre com as condições da corrida, incluindo várias das respostas aqui. Historicamente, você pode pensar cuidadosamente sobre as condições da corrida e escrevê-lo, ou pode usar uma biblioteca de terceiros como o Guava do Google (como sugeriu a resposta de Spina.) Ou pode escrever um código de buggy.

Mas a partir do JDK 7, há boas notícias! A própria biblioteca padrão Java agora oferece uma solução (não atrevida) adequada para esse problema. Você deseja java.nio.file.Files # createTempDirectory () . A partir da documentação :

public static Path createTempDirectory(Path dir,
                       String prefix,
                       FileAttribute<?>... attrs)
                                throws IOException

Cria um novo diretório no diretório especificado, usando o prefixo fornecido para gerar seu nome. O caminho resultante está associado ao mesmo sistema de arquivos que o diretório fornecido.

Os detalhes de como o nome do diretório é construído dependem da implementação e, portanto, não são especificados. Sempre que possível, o prefixo é usado para construir nomes de candidatos.

Isso efetivamente resolve o relatório de erros embaraçosamente antigo no rastreador de erros da Sun, que solicitava exatamente essa função.

Greg Price
fonte
35

Este é o código fonte do arquivo Files.createTempDir () da biblioteca Guava. Não é tão complexo quanto você imagina:

public static File createTempDir() {
  File baseDir = new File(System.getProperty("java.io.tmpdir"));
  String baseName = System.currentTimeMillis() + "-";

  for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
    File tempDir = new File(baseDir, baseName + counter);
    if (tempDir.mkdir()) {
      return tempDir;
    }
  }
  throw new IllegalStateException("Failed to create directory within "
      + TEMP_DIR_ATTEMPTS + " attempts (tried "
      + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')');
}

Por padrão:

private static final int TEMP_DIR_ATTEMPTS = 10000;

Veja aqui

Andres Kievsky
fonte
28

Não use deleteOnExit()mesmo se você explicitamente excluí-lo mais tarde.

O Google 'deleteonexit is evil' para mais informações, mas a essência do problema é:

  1. deleteOnExit() exclui apenas os encerramentos normais da JVM, não trava ou mata o processo da JVM.

  2. deleteOnExit() exclui apenas no encerramento da JVM - não é bom para processos de servidor de longa execução porque:

  3. O pior de todos - deleteOnExit()consome memória para cada entrada de arquivo temporário. Se seu processo estiver em execução por meses ou criar muitos arquivos temporários em pouco tempo, você consome memória e nunca a libera até que a JVM seja encerrada.

Desenvolvedor Dude
fonte
11
Temos uma JVM em que os arquivos de classe e jar ficam ocultos por baixo, criados pela JVM, e essas informações extras levam um tempo para serem excluídas. Ao fazer reimplantações a quente em contêineres da Web que explodem WARs, a JVM pode literalmente levar alguns minutos para limpar depois de terminar, mas antes de sair quando estiver em execução por algumas horas.
Thorbjørn Ravn Andersen
20

A partir do Java 1.7 createTempDirectory(prefix, attrs)e createTempDirectory(dir, prefix, attrs)estão incluídos nojava.nio.file.Files

Exemplo: File tempDir = Files.createTempDirectory("foobar").toFile();

buscador
fonte
14

Isto é o que eu decidi fazer pelo meu próprio código:

/**
 * Create a new temporary directory. Use something like
 * {@link #recursiveDelete(File)} to clean this directory up since it isn't
 * deleted automatically
 * @return  the new directory
 * @throws IOException if there is an error creating the temporary directory
 */
public static File createTempDir() throws IOException
{
    final File sysTempDir = new File(System.getProperty("java.io.tmpdir"));
    File newTempDir;
    final int maxAttempts = 9;
    int attemptCount = 0;
    do
    {
        attemptCount++;
        if(attemptCount > maxAttempts)
        {
            throw new IOException(
                    "The highly improbable has occurred! Failed to " +
                    "create a unique temporary directory after " +
                    maxAttempts + " attempts.");
        }
        String dirName = UUID.randomUUID().toString();
        newTempDir = new File(sysTempDir, dirName);
    } while(newTempDir.exists());

    if(newTempDir.mkdirs())
    {
        return newTempDir;
    }
    else
    {
        throw new IOException(
                "Failed to create temp dir named " +
                newTempDir.getAbsolutePath());
    }
}

/**
 * Recursively delete file or directory
 * @param fileOrDir
 *          the file or dir to delete
 * @return
 *          true iff all files are successfully deleted
 */
public static boolean recursiveDelete(File fileOrDir)
{
    if(fileOrDir.isDirectory())
    {
        // recursively delete contents
        for(File innerFile: fileOrDir.listFiles())
        {
            if(!FileUtilities.recursiveDelete(innerFile))
            {
                return false;
            }
        }
    }

    return fileOrDir.delete();
}
Keith
fonte
2
Isso é inseguro. Veja o comentário de Joachim Sauer na primeira opção (igualmente insegura). A maneira correta de verificar a existência de um arquivo ou diretório e capturar o nome do arquivo, atomicamente, é criando o arquivo ou diretório.
Zbyszek
11
@zbyszek javadocs dizem "O UUID é gerado usando um gerador de números pseudo-aleatórios criptograficamente forte". Dado que como um processo malicioso cria um diretório com o mesmo nome entre existe () e mkdirs (). De fato, olhando para isso agora, acho que meu teste existe () pode ser um pouco bobo.
23412 Keith
Keith: UUID sendo seguro ou não não é crucial neste caso. É o suficiente para as informações sobre o nome que você consultou de alguma forma "vazar". Por exemplo, digamos que o arquivo que está sendo criado esteja em um sistema de arquivos NFS e o invasor possa ouvir (passivamente) os pacotes. Ou o estado do gerador aleatório vazou. No meu comentário, eu disse que sua solução é igualmente insegura como a resposta aceita, mas isso não é justo: o aceito é trivial para derrotar com inotify, e esse é muito mais difícil de derrotar. No entanto, em alguns cenários, é certamente possível.
precisa saber é
2
Eu tive o mesmo pensamento e implementei uma solução usando UUIDs aleatórios como este. Nenhuma verificação existe, apenas uma tentativa de criar - o RNG forte usado pelo método randomUUID praticamente não garante colisões (pode ser usado para gerar chaves primárias em tabelas de banco de dados, eu mesmo fiz isso e nunca conheci uma colisão), tão confiante. Se alguém não tiver certeza, consulte stackoverflow.com/questions/2513573/…
brabster
Se você observar a implementação do Java, eles apenas geram nomes aleatórios até que não haja colisão. Suas tentativas máximas são infinitas. Portanto, se alguém malicioso continuasse tentando adivinhar o nome do arquivo / diretório, ele seria repetido para sempre. Aqui está um link para a fonte: hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/9fb81d7a2f16/src/share/… Eu estava pensando que, de alguma forma, poderia bloquear o sistema de arquivos para gerar atomicamente um nome exclusivo e crie o diretório, mas acho que não faz isso de acordo com o código fonte.
dosentmatter
5

Bem, "createTempFile" na verdade cria o arquivo. Então, por que não excluí-lo primeiro e depois fazer o mkdir?

Paul Tomblin
fonte
11
Você deve sempre verificar o valor de retorno para mkdir (). Se isso for falso, significa que o diretório já existia. Isso pode causar problemas de segurança, portanto, considere se isso deve gerar um erro no seu aplicativo ou não.
22411 Sarel Botha
11
Veja a nota sobre a condição de corrida na outra resposta.
Volker Stolz
Isso eu gosto, exceto a corrida
Martin Wickman
4

Este código deve funcionar razoavelmente bem:

public static File createTempDir() {
    final String baseTempPath = System.getProperty("java.io.tmpdir");

    Random rand = new Random();
    int randomInt = 1 + rand.nextInt();

    File tempDir = new File(baseTempPath + File.separator + "tempDir" + randomInt);
    if (tempDir.exists() == false) {
        tempDir.mkdir();
    }

    tempDir.deleteOnExit();

    return tempDir;
}
matt b
fonte
3
E se o diretório já existir e você não tiver acesso de leitura / gravação a ele, e se for um arquivo comum? Você também tem uma condição de corrida lá.
Jeremy Huiskamp
2
Além disso, deleteOnExit não excluirá diretórios não vazios.
Trenton
3

Conforme discutido nesta RFE e seus comentários, você pode ligar tempDir.delete()primeiro. Ou você pode usar System.getProperty("java.io.tmpdir")e criar um diretório lá. De qualquer forma, lembre-se de ligar tempDir.deleteOnExit()ou o arquivo não será excluído depois que você terminar.

Michael Myers
fonte
Essa propriedade não é chamada "java.io.tmpdir", não "... temp"? Veja java.sun.com/j2se/1.4.2/docs/api/java/io/File.html
Andrew Swan
Sim. Eu deveria ter verificado antes de repetir o que li.
Michael Myers
O java.io.tmpdir é compartilhado, portanto você precisa fazer todo o vodu de sempre para evitar pisar nos outros dedos.
Thorbjørn Ravn Andersen
3

Apenas para conclusão, este é o código da biblioteca do google goiaba. Não é meu código, mas acho que é valioso mostrá-lo aqui neste tópico.

  /** Maximum loop count when creating temp directories. */
  private static final int TEMP_DIR_ATTEMPTS = 10000;

  /**
   * Atomically creates a new directory somewhere beneath the system's temporary directory (as
   * defined by the {@code java.io.tmpdir} system property), and returns its name.
   *
   * <p>Use this method instead of {@link File#createTempFile(String, String)} when you wish to
   * create a directory, not a regular file. A common pitfall is to call {@code createTempFile},
   * delete the file and create a directory in its place, but this leads a race condition which can
   * be exploited to create security vulnerabilities, especially when executable files are to be
   * written into the directory.
   *
   * <p>This method assumes that the temporary volume is writable, has free inodes and free blocks,
   * and that it will not be called thousands of times per second.
   *
   * @return the newly-created directory
   * @throws IllegalStateException if the directory could not be created
   */
  public static File createTempDir() {
    File baseDir = new File(System.getProperty("java.io.tmpdir"));
    String baseName = System.currentTimeMillis() + "-";

    for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
      File tempDir = new File(baseDir, baseName + counter);
      if (tempDir.mkdir()) {
        return tempDir;
      }
    }
    throw new IllegalStateException(
        "Failed to create directory within "
            + TEMP_DIR_ATTEMPTS
            + " attempts (tried "
            + baseName
            + "0 to "
            + baseName
            + (TEMP_DIR_ATTEMPTS - 1)
            + ')');
  }
Arne
fonte
2

Eu tenho o mesmo problema, então essa é apenas mais uma resposta para aqueles que estão interessados, e é semelhante a um dos itens acima:

public static final String tempDir = System.getProperty("java.io.tmpdir")+"tmp"+System.nanoTime();
static {
    File f = new File(tempDir);
    if(!f.exists())
        f.mkdir();
}

E para o meu aplicativo, decidi adicionar uma opção para limpar a temperatura na saída, adicionando um gancho de desligamento:

Runtime.getRuntime().addShutdownHook(new Thread() {
        @Override
        public void run() {
            //stackless deletion
            String root = MainWindow.tempDir;
            Stack<String> dirStack = new Stack<String>();
            dirStack.push(root);
            while(!dirStack.empty()) {
                String dir = dirStack.pop();
                File f = new File(dir);
                if(f.listFiles().length==0)
                    f.delete();
                else {
                    dirStack.push(dir);
                    for(File ff: f.listFiles()) {
                        if(ff.isFile())
                            ff.delete();
                        else if(ff.isDirectory())
                            dirStack.push(ff.getPath());
                    }
                }
            }
        }
    });

O método exclui todos os subdiretórios e arquivos antes de excluir o temp , sem usar o callstack (que é totalmente opcional e você pode fazê-lo com recursão neste momento), mas quero estar do lado seguro.

imotivo
fonte
2

Como você pode ver nas outras respostas, nenhuma abordagem padrão surgiu. Portanto, você já mencionou o Apache Commons, proponho a seguinte abordagem usando o FileUtils do Apache Commons IO :

/**
 * Creates a temporary subdirectory in the standard temporary directory.
 * This will be automatically deleted upon exit.
 * 
 * @param prefix
 *            the prefix used to create the directory, completed by a
 *            current timestamp. Use for instance your application's name
 * @return the directory
 */
public static File createTempDirectory(String prefix) {

    final File tmp = new File(FileUtils.getTempDirectory().getAbsolutePath()
            + "/" + prefix + System.currentTimeMillis());
    tmp.mkdir();
    Runtime.getRuntime().addShutdownHook(new Thread() {

        @Override
        public void run() {

            try {
                FileUtils.deleteDirectory(tmp);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    });
    return tmp;

}

Isso é preferido, pois o apache comunica a biblioteca que mais se aproxima do "padrão" solicitado e funciona com o JDK 7 e versões mais antigas. Isso também retorna uma instância de arquivo "antiga" (que é baseada em fluxo) e não uma instância de caminho "nova" (que é baseada em buffer e seria o resultado do método getTemporaryDirectory () do JDK7) -> Portanto, ele retorna o que a maioria das pessoas precisa quando eles querem criar um diretório temporário.

Carsten Engelke
fonte
1

Gosto das várias tentativas de criar um nome único, mas mesmo essa solução não descarta uma condição de corrida. Outro processo pode ocorrer após o teste exists()e a if(newTempDir.mkdirs())invocação do método. Não tenho idéia de como tornar isso completamente seguro sem recorrer ao código nativo, que eu presumo é o que está escondido dentro dele File.createTempFile().

Chris Lott
fonte
1

Antes do Java 7, você também podia:

File folder = File.createTempFile("testFileUtils", ""); // no suffix
folder.delete();
folder.mkdirs();
folder.deleteOnExit();
geri
fonte
11
Código legal. Mas infelizmente "deleteOnExit ()" não funcionará, pois o Java não pode excluir a pasta inteira de uma só vez. Você precisa excluir todos os arquivos recursivamente: /
Adam Taras
1

Tente este pequeno exemplo:

Código:

try {
    Path tmpDir = Files.createTempDirectory("tmpDir");
    System.out.println(tmpDir.toString());
    Files.delete(tmpDir);
} catch (IOException e) {
    e.printStackTrace();
}


Importações:
java.io.IOException
java.nio.file.Files
java.nio.file.Path

Saída do console na máquina Windows:
C: \ Users \ userName \ AppData \ Local \ Temp \ tmpDir2908538301081367877

Comentário:
Files.createTempDirectory gera ID exclusivo automaticamente - 2908538301081367877.

Nota:
Leia o seguinte para excluir diretórios recursivamente:
Exclua diretórios recursivamente em Java

DigitShifter
fonte
0

Usar File#createTempFilee deletecriar um nome exclusivo para o diretório parece ok. Você deve adicionar a ShutdownHookpara excluir o diretório (recursivamente) no encerramento da JVM.

ordnungswidrig
fonte
Um gancho de desligamento é complicado. O arquivo # deleteOnExit também não funcionaria?
21711 Daniel Hiller
2
#deleteOnExit não funcionou para mim - acredito que não excluirá diretórios não vazios.
muriloq
Implementei um teste rápido correr com Java 8, mas a pasta temporária não foi excluída, consulte pastebin.com/mjgG70KG
geri