Comment créer un répertoire / dossier temporaire en Java?

Existe-t-il un moyen standard et fiable de créer un répertoire temporaire dans une application Java? Il y a une entrée dans la firebase database des problèmes de Java , qui contient un peu de code dans les commentaires, mais je me demande s’il existe une solution standard dans l’une des bibliothèques habituelles (Apache Commons, etc.)?

    Si vous utilisez JDK 7, utilisez la nouvelle classe Files.createTempDirectory pour créer le répertoire temporaire.

    Avant JDK 7, cela devrait le faire:

    public static File createTempDirectory() throws IOException { final File temp; temp = File.createTempFile("temp", Long.toSsortingng(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); } 

    Vous pouvez faire de meilleures exceptions (sous-classe IOException) si vous le souhaitez.

    La bibliothèque Google Guava contient une tonne d’utilitaires utiles. On notera ici la classe Files . Il a un tas de méthodes utiles, y compris:

     File myTempDir = Files.createTempDir(); 

    Cela fait exactement ce que vous avez demandé en une seule ligne. Si vous lisez la documentation ici, vous verrez que l’adaptation proposée de File.createTempFile("install", "dir") introduit généralement des vulnérabilités de sécurité.

    Si vous avez besoin d’un répertoire temporaire pour tester et que vous utilisez jUnit, @Rule avec TemporaryFolder résout votre problème:

     @Rule public TemporaryFolder folder = new TemporaryFolder(); 

    De la documentation :

    La règle TemporaryFolder permet la création de fichiers et de dossiers dont la suppression est garantie à la fin de la méthode de test (qu’elle soit réussie ou non)


    Mettre à jour:

    Si vous utilisez JUnit Jupiter (version 5.1.1 ou ultérieure), vous avez la possibilité d’utiliser JUnit Pioneer, qui est le pack d’extension JUnit 5.

    Copié à partir de la documentation du projet :

    Par exemple, le test suivant enregistre l’extension pour une méthode de test unique, crée et écrit un fichier dans le répertoire temporaire et vérifie son contenu.

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

    Plus d’infos dans JavaDoc et JavaDoc de TempDirectory

    Gradle:

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

    Maven:

      org.junit-pioneer junit-pioneer 0.1.2 test  

    Le code écrit de manière naïve pour résoudre ce problème souffre de conditions de course, y compris plusieurs des réponses ici. Historiquement, vous pouviez réfléchir soigneusement aux conditions de course et l’écrire vous-même, ou vous pouviez utiliser une bibliothèque tierce telle que celle de Google (comme l’a suggéré la réponse de Spina).

    Mais à partir de JDK 7, il y a de bonnes nouvelles! La bibliothèque standard Java elle-même fournit désormais une solution (non-racée) fonctionnant correctement à ce problème. Vous voulez java.nio.file.Files # createTempDirectory () . De la documentation :

     public static Path createTempDirectory(Path dir, Ssortingng prefix, FileAtsortingbute... attrs) throws IOException 

    Crée un nouveau répertoire dans le répertoire spécifié, en utilisant le préfixe donné pour générer son nom. Le chemin résultant est associé au même système de fichiers que le répertoire donné.

    Les détails sur la construction du nom du répertoire dépendent de l’implémentation et ne sont donc pas spécifiés. Dans la mesure du possible, le préfixe est utilisé pour construire des noms de candidats.

    Cela résout effectivement le rapport de bogue extrêmement gênant dans le tracker de bogue Sun qui demandait une telle fonction.

    Ceci est le code source de la bibliothèque Guava Files.createTempDir (). C’est nulle part aussi complexe que vous pourriez le penser:

     public static File createTempDir() { File baseDir = new File(System.getProperty("java.io.tmpdir")); Ssortingng 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) + ')'); } 

    Par défaut:

     private static final int TEMP_DIR_ATTEMPTS = 10000; 

    Vois ici

    N’utilisez pas deleteOnExit() même si vous le supprimez explicitement ultérieurement.

    Google «deleteonexit is evil» pour plus d’informations, mais l’essentiel du problème est:

    1. deleteOnExit() supprime uniquement les arrêts JVM normaux, ne bloque pas ou ne deleteOnExit() pas le processus JVM.

    2. deleteOnExit() ne supprime que lors de l’arrêt de la machine deleteOnExit() JVM shutdown), ce qui ne convient pas aux processus serveur à exécution longue car:

    3. Le plus mauvais de tous – deleteOnExit() consum de la mémoire pour chaque entrée de fichier temporaire. Si votre processus s’exécute pendant des mois ou crée beaucoup de fichiers temporaires en peu de temps, vous consumz de la mémoire et ne la libérez jamais tant que la JVM n’est pas arrêtée.

    À partir de Java 1.7, createTempDirectory(prefix, attrs) et createTempDirectory(dir, prefix, attrs) sont inclus dans java.nio.file.Files

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

    C’est ce que j’ai décidé de faire pour mon propre code:

     /** * 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."); } Ssortingng dirName = UUID.randomUUID().toSsortingng(); 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(); } 

    Eh bien, “createTempFile” crée réellement le fichier. Alors pourquoi ne pas simplement le supprimer en premier, puis faire le mkdir dessus?

    Comme indiqué dans cette RFE et ses commentaires, vous pouvez appeler tempDir.delete() premier. Ou vous pouvez utiliser System.getProperty("java.io.tmpdir") et y créer un répertoire. Quoi qu’il en soit, n’oubliez pas d’appeler tempDir.deleteOnExit() ou le fichier ne sera pas supprimé une fois que vous aurez terminé.

    Juste pour compléter, c’est le code de la bibliothèque google guava. Ce n’est pas mon code, mais je pense qu’il est important de le montrer ici dans ce fil.

      /** 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(Ssortingng, Ssortingng)} 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")); Ssortingng 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) + ')'); }

    J’ai eu le même problème donc c’est juste une autre réponse pour ceux qui sont intéressés, et c’est similaire à l’un des précédents:

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

    Et pour mon application, j’ai décidé d’append une option pour effacer la température à la sortie, j’ai donc ajouté un crochet d’arrêt:

     Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { //stackless deletion Ssortingng root = MainWindow.tempDir; Stack dirStack = new Stack(); dirStack.push(root); while(!dirStack.empty()) { Ssortingng 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()); } } } } }); 

    La méthode supprime tous les sous-répertoires et fichiers avant de supprimer la temp , sans utiliser le callstack (qui est totalement facultatif et vous pouvez le faire avec la récursivité à ce stade), mais je veux être sûr.

    Ce code devrait fonctionner raisonnablement bien:

     public static File createTempDir() { final Ssortingng 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; } 

    J’aime les multiples tentatives de création d’un nom unique, mais même cette solution n’exclut pas une situation de concurrence. Un autre processus peut se glisser après le test pour if(newTempDir.mkdirs()) et l’invocation de la méthode if(newTempDir.mkdirs()) . Je n’ai aucune idée de la façon de rendre complètement ce coffre-fort sans recourir au code natif, ce que je présume être ce qui est enfoui dans File.createTempFile() .

    Comme vous pouvez le voir dans les autres réponses, aucune approche standard n’a surgi. Vous avez donc déjà mentionné Apache Commons, je propose l’approche suivante en utilisant FileUtils d’ 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(Ssortingng 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; } 

    Ceci est préférable car Apache communique avec la bibliothèque qui se rapproche le plus du “standard” demandé et fonctionne à la fois avec JDK 7 et les versions antérieures. Cela renvoie également une “ancienne” instance de fichier (basée sur un stream) et non une “nouvelle” instance de chemin (basée sur la mémoire tampon et résultant de la méthode getTemporaryDirectory () de JDK7). ils veulent créer un répertoire temporaire.

    Avant Java 7, vous pouviez également:

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

    Utiliser File#createTempFile et delete pour créer un nom unique pour le répertoire semble correct. Vous devez append un ShutdownHook pour supprimer le répertoire (récursivement) sur JVM shutdown.