web-dev-qa-db-fra.com

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 ne entrée dans la base de données 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.)?

330
Peter Becker

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

Path tempDirWithPrefix = Files.createTempDirectory(prefix);

Avant le JDK 7, cela devrait le faire:

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

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

359
TofuBeer

La bibliothèque Google Guava propose une multitude d'utilitaires utiles. Un de la note ici est le classe de fichiers . Il a un tas de méthodes utiles, notamment:

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 en matière de sécurité.

180
Spina

Si vous avez besoin d'un répertoire temporaire pour les tests et que vous utilisez jUnit, @Rule ainsi que TemporaryFolder résolvent 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 réussisse ou échoue).


Mise à jour:

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

Copié à partir du documentation du projet :

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

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

Plus d'infos dans le JavaDoc et le JavaDoc de 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>

Mise à jour 2:

L'annotation @ TempDir a été ajoutée à la version JUniter Jupiter 5.4.0 de JUnit en tant que fonctionnalité expérimentale. Exemple copié à partir de Guide de l'utilisateur de 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));
}
154
matsev

Le code naïvement écrit pour résoudre ce problème souffre de conditions de concurrence, y compris de plusieurs réponses. Historiquement, vous pouviez bien réfléchir aux conditions de concurrence et l'écrire vous-même, ou utiliser une bibliothèque tierce telle que Google's Guava (comme le suggérait la réponse de Spina). Vous pouviez aussi écrire du code buggy.

Mais à partir de 7 JDK, il y a de bonnes nouvelles! La bibliothèque standard Java fournit elle-même une solution (non-racée) fonctionnant correctement pour résoudre 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 indiqué 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 relatifs à la construction du nom du répertoire dépendent de la mise en oeuvre et ne sont donc pas spécifiés. Dans la mesure du possible, le préfixe est utilisé pour construire les noms de candidats.

Ceci résout efficacement le rapport de bogue extrêmement embarrassant dans le traqueur de bogues Sun qui demandait une telle fonction.

39
Greg Price

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

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

Par défaut:

private static final int TEMP_DIR_ATTEMPTS = 10000;

voir ici

34
Andres Kievsky

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 le suivant:

  1. deleteOnExit() supprime uniquement les arrêts normaux de la machine virtuelle Java, pas pour bloquer ou tuer le processus de la machine virtuelle Java.

  2. deleteOnExit() supprime uniquement la fermeture de la machine virtuelle Java - ce qui n'est pas recommandé pour les processus de serveur longs, car:

  3. Le plus diabolique de tous - deleteOnExit() consomme de la mémoire pour chaque entrée de fichier temporaire. Si votre processus est en cours d'exécution depuis des mois ou crée un grand nombre de fichiers temporaires en peu de temps, vous utilisez de la mémoire et vous ne la libérez jamais tant que la JVM n'est pas arrêtée.

25
Developer Dude

À 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();

20
seeker

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();
}
14
Keith

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

5
Paul Tomblin

Juste pour compléter, voici le code de la librairie google guava. Ce n'est pas mon code, mais je pense qu'il est précieux 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)
            + ')');
  }
3
Arne

Comme indiqué dans this RFE et ses commentaires, vous pouvez d'abord appeler tempDir.delete(). Ou vous pouvez utiliser System.getProperty("Java.io.tmpdir") et créer un répertoire à cet endroit. Quoi qu'il en soit, rappelez-vous d'appeler tempDir.deleteOnExit(), sinon le fichier ne sera pas supprimé une fois que vous aurez terminé.

3
Michael Myers

Ce code devrait fonctionner assez bien:

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;
}
3
matt b

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

public static final String 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'ajouter une option pour effacer le temp à la sortie. J'ai donc ajouté un crochet d'arrêt:

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

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

2
immoteous

Comme vous pouvez le constater dans les autres réponses, aucune approche standard n’est apparue. Par conséquent, vous avez déjà mentionné Apache Commons, je propose l'approche suivante en utilisant FileUtils à partir de 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;

}

Ceci est préférable car Apache communa la bibliothèque qui se rapproche le plus du "standard" demandé et qui fonctionne avec JDK 7 et les versions antérieures. Cela retourne également une "ancienne" instance de fichier (basée sur le flux) et non une "nouvelle" instance de chemin (basée sur un tampon et résultant de la méthode getTemporaryDirectory () de JDK7) -> Par conséquent, elle renvoie ce dont la plupart des gens ont besoin ils veulent créer un répertoire temporaire.

2
Carsten Engelke

Avant Java 7, vous pouviez également:

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

J'aime les multiples tentatives de création d'un nom unique, mais même cette solution n'exclut pas une situation de concurrence critique. Un autre processus peut se glisser après le test de exists() et l'invocation de la méthode if(newTempDir.mkdirs()). Je ne sais pas comment sécuriser complètement cela sans avoir recours au code natif, ce qui, je suppose, est ce qui est enfoui à l'intérieur de File.createTempFile().

1
Chris Lott

Utiliser File#createTempFile et delete pour créer un nom unique pour le répertoire semble correct. Vous devez ajouter un ShutdownHook pour supprimer le répertoire (de manière récursive) à l’arrêt de la machine virtuelle Java.

0
ordnungswidrig