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

Existe uma maneira padrão e confiável de criar um diretório temporário dentro de um aplicativo Java? Há uma input no database de problemas do Java , que tem um pouco de código nos comentários, mas gostaria de saber se existe uma solução padrão para ser encontrada em uma das bibliotecas usuais (Apache Commons etc.)?

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

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ê poderia fazer melhores exceções (subclass IOException) se quiser.

A biblioteca do Google Guava tem muitos utilitários úteis. Uma das notas aqui é a class Files . Tem um monte de methods ú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 de File.createTempFile("install", "dir") normalmente introduz vulnerabilidades de segurança.

Se você precisar de um diretório temporário para testes e estiver usando o jUnit, o @Rule juntamente com o TemporaryFolder solucionará seu problema:

 @Rule public TemporaryFolder folder = new TemporaryFolder(); 

Da documentação :

A Regra da Pasta Temporária permite a criação de arquivos e pastas com a garantia de serem excluídos quando o método de teste é concluído (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 de 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:

  org.junit-pioneer junit-pioneer 0.1.2 test  

Código escrito ingenuamente para resolver este problema sofre de condições de corrida, incluindo várias das respostas aqui. Historicamente, você poderia pensar cuidadosamente sobre as condições da corrida e escrevê-lo você mesmo, ou você poderia usar uma biblioteca de terceiros como o goiaba do Google (como sugerido por Spina). Ou você poderia escrever código com bugs.

Mas a partir do JDK 7, há boas notícias! A própria biblioteca padrão Java agora fornece uma solução que funciona corretamente (sem atratividade) para esse problema. Você deseja java.nio.file.Files # createTempDirectory () . 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 FileSystem 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 relato de bug antigo e embaraçoso no rastreador de bugs da Sun, que pedia apenas uma dessas funções.

Este é o código-fonte do Files.createTempDir () da biblioteca Guava. Não é tão complexo como 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

Não use deleteOnExit() mesmo que você o exclua explicitamente mais tarde.

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 nem mata o processo da JVM.

  2. deleteOnExit() exclui apenas o encerramento da JVM – não é bom para processos longos do servidor porque:

  3. O mais maligno de todos – deleteOnExit() consome memory para cada input de arquivo temporário. Se o processo estiver em execução por meses ou criar muitos arquivos temporários em pouco tempo, você consome memory e nunca a libera até que a JVM seja encerrada.

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

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

Isto é o que decidi fazer para o 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(); } 

Bem, “createTempFile” realmente cria o arquivo. Então, por que não apenas excluí-lo primeiro e depois fazer o mkdir nele?

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

Apenas para conclusão, este é o código da biblioteca google goava. Não é o meu código, mas acho que é útil 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. * * 

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. * *

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

Eu tenho o mesmo problema, então esta é apenas outra 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 meu aplicativo, decidi adicionar uma opção para limpar a temperatura na saída, então adicionei um gancho de desligamento:

 Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { //stackless deletion String root = MainWindow.tempDir; Stack dirStack = new Stack(); 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 apaga todos os subdiretórios e arquivos antes de deletar temp , sem usar o callstack (o que é totalmente opcional e você pode fazer isso com recursion neste momento), mas eu quero estar no lado seguro.

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

Eu gosto das múltiplas tentativas de criar um nome único, mas mesmo essa solução não descarta uma condição de corrida. Outro processo pode entrar depois do teste para exists() e a invocação do método if(newTempDir.mkdirs()) . Eu não tenho idéia de como fazer isso completamente seguro sem recorrer ao código nativo, o que eu presumo é o que está enterrado dentro de File.createTempFile() .

Como você pode ver nas outras respostas, nenhuma abordagem padrão surgiu. Portanto, você já mencionou o Apache Commons, eu 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 é preferível, já que o apache é a biblioteca mais próxima 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 stream) e não uma instância de caminho “nova” (que é baseada em buffer e seria o resultado do método getTemporaryDirectory () do JDK7). eles querem criar um diretório temporário.

Antes do Java 7 você também poderia:

 File folder = File.createTempFile("testFileUtils", ""); // no suffix folder.delete(); folder.mkdirs(); folder.deleteOnExit(); 

Usando o File#createTempFile e delete para criar um nome exclusivo para o diretório parece ok. Você deve include um ShutdownHook para excluir o diretório (recursivamente) no encerramento da JVM.