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


Answers

La bibliothèque Google Guava a une tonne d'utilitaires utiles. Une des notations ici est 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 ligne. Si vous lisez la documentation ici, vous verrez que l'adaptation proposée de File.createTempFile("install", "dir") introduit généralement des failles de sécurité.

Question

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 base de données 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.)?




Naïvement écrit le code pour résoudre ce problème souffre des conditions de course, y compris plusieurs des réponses ici. Historiquement, vous pouviez réfléchir sérieusement aux conditions de course et l'écrire vous-même, ou vous pouviez utiliser une bibliothèque tierce comme Guava de Google (comme suggéré par Spina). Ou vous pouviez écrire du code buggé.

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

public static Path createTempDirectory(Path dir,
                       String prefix,
                       FileAttribute<?>... 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 FileSystem que le répertoire donné.

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

Ceci résout efficacement le rapport de bogue angoissant dans le traqueur de bogues de Sun qui a demandé juste une telle fonction.




Avant Java 7, vous pouvez également:

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



J'aime les tentatives multiples de créer un nom unique, mais même cette solution n'exclut pas une condition de concurrence. Un autre processus peut glisser après le test de exists() et l'invocation de méthode if(newTempDir.mkdirs()) . Je n'ai aucune idée de comment rendre ceci complètement sûr sans recourir au code natif, qui je suppose est ce qui est enterré dans File.createTempFile() .




Comme indiqué dans cette RFE et ses commentaires, vous pouvez d' tempDir.delete() appeler tempDir.delete() . Ou vous pouvez utiliser System.getProperty("java.io.tmpdir") et y créer un répertoire. De toute façon, vous devriez vous rappeler d'appeler tempDir.deleteOnExit() , ou le fichier ne sera pas supprimé une fois que vous avez terminé.




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.");
        }
        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();
}



N'utilisez pas deleteOnExit() même si vous le supprimez explicitement plus tard.

Google 'deleteonexit est mal' pour plus d'informations, mais l'essentiel du problème est:

  1. deleteOnExit() ne supprime que pour les arrêts JVM normaux, ne se bloque pas ou ne tue pas le processus JVM.

  2. deleteOnExit() ne supprime que sur l'arrêt de la machine deleteOnExit() - ce qui n'est pas bon pour les processus serveur à exécution longue car:

  3. Le plus mauvais de tous - deleteOnExit() consomme de la mémoire pour chaque entrée de fichier temporaire. Si votre processus s'exécute pendant des mois ou crée un grand nombre de fichiers temporaires dans un court laps de temps, vous consommez de la mémoire et ne la libérez jamais tant que la machine virtuelle Java ne s'est pas arrêtée.




Juste pour l'achèvement, c'est le code de la bibliothèque google guava. Ce n'est pas mon code, mais je pense qu'il vaut la peine 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.
   *
   * <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)
            + ')');
  }



Related