Come creare una directory / cartella temporanea in Java?


Risposte:


390

Se si utilizza JDK 7, utilizzare la nuova classe Files.createTempDirectory per creare la directory temporanea.

Path tempDirWithPrefix = Files.createTempDirectory(prefix);

Prima di JDK 7 questo dovrebbe farlo:

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

Se lo desideri, potresti fare eccezioni migliori (sottoclasse IOException).


12
Questo è pericoloso. Java è noto per non eliminare immediatamente i file, quindi a volte mkdir potrebbe non funzionare
Demiurg

4
@Demiurg L'unico caso in cui un file non viene eliminato immediatamente è su Windows quando il file è già aperto (ad esempio potrebbe essere aperto da uno scanner antivirus). Hai altra documentazione da mostrare altrimenti (sono curioso di sapere cose del genere :-)? Se si verifica regolarmente, il codice sopra riportato non funzionerà, se è raro, il collegamento della chiamata al codice sopra riportato fino a quando non si verifica l'eliminazione (o viene raggiunto un numero massimo di tentativi) funzionerebbe.
TofuBeer,

6
@Demiurg Java è noto per non eliminare immediatamente i file. È vero, anche se non lo apri. Quindi, un modo più sicuro è temp.delete(); temp = new File(temp.getPath + ".d"); temp.mkdir(); ..., temp.delete();.
Xiè Jìléi,

102
Questo codice ha una condizione di competizione tra delete()e mkdir(): Un processo dannoso potrebbe nel frattempo creare la directory di destinazione (prendendo il nome del file creato di recente). Vedi Files.createTempDir()per un'alternativa.
Joachim Sauer,

11
Mi piace il ! distinguersi, troppo facile per perderlo. Ho letto molto codice scritto dagli studenti ... se (! I) è abbastanza comune da essere fastidioso :-)
TofuBeer,

182

La libreria di Google Guava ha moltissime utilità utili. Una nota qui è la classe Files . Ha un sacco di metodi utili tra cui:

File myTempDir = Files.createTempDir();

Questo fa esattamente quello che hai chiesto in una riga. Se leggi la documentazione qui vedrai che l'adattamento proposto in File.createTempFile("install", "dir")genere introduce vulnerabilità della sicurezza.


Mi chiedo a quale vulnerabilità ti riferisci. Questo approccio non sembra creare una race condition in quanto si suppone che File.mkdir () fallisca se tale directory esiste già (creata da un utente malintenzionato). Non credo che questa chiamata seguirà neanche un link simbolico malevolo. Potresti chiarire cosa intendi?
abb

3
@abb: non conosco i dettagli delle condizioni di gara menzionati nella documentazione di Guava. Ho il sospetto che la documentazione sia corretta dato che specifica specificamente il problema.
Spina,

1
@abb Hai ragione. Fintanto che il ritorno di mkdir () è controllato, sarebbe sicuro. Il codice Spina punta a utilizzare questo metodo mkdir (). grepcode.com/file/repo1.maven.org/maven2/com.google.guava/guava/… . Questo è solo un potenziale problema sui sistemi Unix quando si utilizza la directory / tmp perché ha il bit sticky abilitato.
Sarel Botha,

@SarelBotha grazie per aver riempito lo spazio vuoto qui. Mi stavo chiedendo pigramente per un po 'di tempo.
Spina,

168

Se hai bisogno di una directory temporanea per il test e stai usando jUnit, @Ruleinsieme a TemporaryFolderrisolve il tuo problema:

@Rule
public TemporaryFolder folder = new TemporaryFolder();

Dalla documentazione :

La regola TemporaryFolder consente la creazione di file e cartelle che sono garantiti per l'eliminazione al termine del metodo di test (se passa o meno)


Aggiornare:

Se si utilizza JUnit Jupiter (versione 5.1.1 o successiva), è possibile utilizzare JUnit Pioneer che è il pacchetto di estensione JUnit 5.

Copiato dalla documentazione del progetto :

Ad esempio, il test seguente registra l'estensione per un singolo metodo di test, crea e scrive un file nella directory temporanea e ne controlla il contenuto.

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

Maggiori informazioni in JavaDoc e JavaDoc di TempDirectory

Gradle:

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

Esperto di:

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

Aggiornamento 2:

L' annotazione @TempDir è stata aggiunta alla versione JUnit Jupiter 5.4.0 come funzionalità sperimentale. Esempio copiato dalla Guida dell'utente di 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));
}

8
Disponibile da JUnit 4.7
Eduard Wirch l'

Non funziona in JUnit 4.8.2 su Windows 7! (Rilascio dei permessi)
eccezione il

2
@CraigRinger: Perché non è saggio fare affidamento su questo?
Adam Parkin,

2
@AdamParkin Onestamente, non mi ricordo più. Spiegazione fallita!
Craig Ringer,

1
Il vantaggio principale di questo approccio è che la directory è gestita da JUnit (creata prima del test ed eliminata in modo ricorsivo dopo il test). E funziona. Se ottieni "temp temp non ancora creata", potrebbe essere perché hai dimenticato @Rule o il campo non è pubblico.
Bogdan Calmac,

42

Il codice scritto in modo ingenuo per risolvere questo problema soffre di condizioni di gara, tra cui alcune delle risposte qui. Storicamente potresti pensare attentamente alle condizioni di gara e scriverlo tu stesso, oppure potresti utilizzare una libreria di terze parti come la Guava di Google (come suggerito dalla risposta di Spina). Oppure potresti scrivere un codice difettoso.

Ma a partire da JDK 7, ci sono buone notizie! La stessa libreria standard Java ora fornisce una soluzione correttamente funzionante (non audace) a questo problema. Volete java.nio.file.Files # createTempDirectory () . Dalla documentazione :

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

Crea una nuova directory nella directory specificata, usando il prefisso specificato per generare il suo nome. Il percorso risultante è associato allo stesso FileSystem della directory specificata.

I dettagli su come viene costruito il nome della directory dipendono dall'implementazione e quindi non vengono specificati. Ove possibile, il prefisso viene utilizzato per costruire nomi candidati.

Ciò risolve efficacemente la segnalazione di bug antica e imbarazzante nel tracker dei bug di Sun che richiedeva proprio tale funzione.


35

Questo è il codice sorgente per Files.createTempDir () della libreria Guava. Non è affatto complesso come potresti pensare:

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

Di default:

private static final int TEMP_DIR_ATTEMPTS = 10000;

Vedere qui


28

Non utilizzare deleteOnExit()anche se lo si elimina esplicitamente in seguito.

Google "deleteonexit is evil" per ulteriori informazioni, ma l'essenza del problema è:

  1. deleteOnExit() elimina solo per i normali arresti JVM, non arresti anomali o interruzione del processo JVM.

  2. deleteOnExit() cancella solo l'arresto di JVM - non va bene per processi server di lunga durata perché:

  3. Il più cattivo di tutti: deleteOnExit()consuma memoria per ogni voce del file temporaneo. Se il processo è in esecuzione da mesi o crea molti file temporanei in breve tempo, si consuma memoria e non si rilascia mai fino allo spegnimento di JVM.


1
Abbiamo una JVM in cui i file di classe e jar ottengono i file nascosti sottostanti creati dalla JVM e questa informazione aggiuntiva richiede parecchio tempo per essere eliminata. Quando si eseguono ridistribuzioni a caldo su container Web che esplodono WAR, JVM può richiedere letteralmente alcuni minuti per ripulire dopo aver terminato, ma prima di uscire dopo aver funzionato per alcune ore.
Thorbjørn Ravn Andersen,

20

A partire da Java 1.7 createTempDirectory(prefix, attrs)e createTempDirectory(dir, prefix, attrs)sono inclusi injava.nio.file.Files

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


14

Questo è quello che ho deciso di fare per il mio codice:

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

2
Questo non è sicuro. Vedi il commento di Joachim Sauer nella prima opzione (altrettanto insicura). Il modo corretto di verificare l'esistenza di un file o di una directory e di catturare atomicamente il nome del file consiste nel creare il file o la directory.
zbyszek,

1
@zbyszek javadocs dice "L'UUID è generato usando un generatore di numeri pseudo casuali crittograficamente forte." Dato che un processo dannoso crea una directory con lo stesso nome tra exist () e mkdirs (). In effetti, guardando questo ora, penso che il mio test esiste () potrebbe essere un po 'sciocco.
Keith,

Keith: UUID essere sicuro o no non è cruciale in questo caso. È sufficiente che le informazioni sul nome richiesto per "in qualche modo" filtrino. Ad esempio, supponiamo che il file che si sta creando sia su un filesystem NFS e che l'attaccante possa ascoltare (passivamente) i pacchetti. O lo stato del generatore casuale è stato trapelato. Nel mio commento ho detto che la tua soluzione è altrettanto insicura della risposta accettata, ma questa non è giusta: quella accettata è banale da sconfiggere con inotify, e questa è molto più difficile da sconfiggere. Tuttavia, in alcuni scenari è certamente possibile.
zbyszek,

2
Ho avuto lo stesso pensiero e ho implementato una soluzione usando UUID casuali come questo. Non esiste alcun controllo, solo un tentativo di creare: un RNG forte utilizzato dal metodo randomUUID praticamente non garantisce collisioni (può essere utilizzato per generare chiavi primarie nelle tabelle DB, fatto da solo e mai conosciuto una collisione), quindi abbastanza sicuro. Se qualcuno non è sicuro, controlla stackoverflow.com/questions/2513573/...
brabster

Se guardi l'implementazione di Java, generano solo nomi casuali fino a quando non ci sono collisioni. I loro tentativi massimi sono infiniti. Quindi, se qualcuno malizioso dovesse continuare a indovinare il nome del tuo file / directory, rimarrebbe per sempre. Ecco un link alla fonte: hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/9fb81d7a2f16/src/share/… Pensavo che potesse in qualche modo bloccare il filesystem in modo da generare atomicamente un nome univoco e creare la directory, ma suppongo che non lo faccia secondo il codice sorgente.
dosentmatter,

5

Bene, "createTempFile" crea effettivamente il file. Quindi perché non cancellarlo prima e poi fare il mkdir su di esso?


1
Dovresti sempre controllare il valore di ritorno per mkdir (). Se questo è falso, significa che la directory esiste già. Ciò può causare problemi di sicurezza, quindi considera se ciò dovrebbe generare un errore nella tua applicazione o meno.
Sarel Botha,

1
Vedi la nota sulle condizioni di gara nell'altra risposta.
Volker Stolz,

Questo mi piace, a parte la gara
Martin Wickman,

4

Questo codice dovrebbe funzionare abbastanza bene:

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
Cosa succede se la directory esiste già e non si dispone dell'accesso in lettura / scrittura ad essa o se si tratta di un file normale? Hai anche una condizione di gara lì.
Jeremy Huiskamp,

2
Inoltre, deleteOnExit non eliminerà le directory non vuote.
Trenton,

3

Come discusso in questa RFE e nei suoi commenti, potresti chiamare per tempDir.delete()primo. Oppure puoi usare System.getProperty("java.io.tmpdir")e creare una directory lì. Ad ogni modo, dovresti ricordarti di chiamare tempDir.deleteOnExit(), altrimenti il ​​file non verrà eliminato dopo aver finito.


Questa proprietà non è chiamata "java.io.tmpdir", non "... temp"? Vedi java.sun.com/j2se/1.4.2/docs/api/java/io/File.html
Andrew Swan

Proprio così. Avrei dovuto verificare prima di ripetere ciò che ho letto.
Michael Myers

Il java.io.tmpdir è condiviso, quindi devi fare tutto il solito voodoo per evitare di calpestare qualcuno.
Thorbjørn Ravn Andersen,

3

Solo per completamento, questo è il codice della libreria guava di google. Non è il mio codice, ma penso che sia prezioso mostrarlo qui in questo thread.

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

2

Ho avuto lo stesso problema, quindi questa è solo un'altra risposta per coloro che sono interessati ed è simile a una delle precedenti:

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 per la mia applicazione, ho deciso di aggiungere un'opzione per cancellare la temperatura all'uscita, quindi ho aggiunto un hook di spegnimento:

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

Il metodo elimina tutti i file secondari e i file prima di eliminare il temp , senza usare il callstack (che è totalmente facoltativo e potresti farlo con ricorsione a questo punto), ma voglio essere al sicuro.


2

Come puoi vedere nelle altre risposte, non è emerso alcun approccio standard. Quindi hai già menzionato Apache Commons, propongo il seguente approccio usando FileUtils di 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;

}

Questo è preferito poiché apache condivide la libreria che si avvicina di più allo "standard" richiesto e funziona sia con JDK 7 che con le versioni precedenti. Ciò restituisce anche un'istanza di file "vecchia" (che è basata sul flusso) e non una "nuova" istanza di percorso (che è basata sul buffer e sarebbe il risultato del metodo getTemporaryDirectory () di JDK7) -> Pertanto restituisce ciò di cui la maggior parte delle persone ha bisogno quando vogliono creare una directory temporanea.


1

Mi piacciono i molteplici tentativi di creare un nome univoco ma anche questa soluzione non esclude una condizione di competizione. Un altro processo può insinuarsi dopo il test exists()e la if(newTempDir.mkdirs())chiamata al metodo. Non ho idea di come renderlo completamente sicuro senza ricorrere al codice nativo, che presumo sia ciò che è sepolto all'interno File.createTempFile().


1

Prima di Java 7 potresti anche:

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

1
Bel codice. Ma sfortunatamente "deleteOnExit ()" non funzionerà, poiché Java non può cancellare l'intera cartella in una sola volta. Devi eliminare tutti i file in modo ricorsivo: /
Adam Taras,

1

Prova questo piccolo esempio:

Codice:

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


Importazioni:
java.io.IOException
java.nio.file.Files
java.nio.file.Path

Output della console su computer Windows:
C: \ Users \ nomeutente \ AppData \ Local \ Temp \ tmpDir2908538301081367877

Commento:
Files.createTempDirectory genera un ID univoco atomaticamente - 2908538301081367877.

Nota:
leggere quanto segue per eliminare ricorsivamente le directory:
eliminare ricorsivamente le directory in Java


0

Usare File#createTempFilee deleteper creare un nome univoco per la directory sembra ok. È necessario aggiungere a ShutdownHookper eliminare la directory (ricorsivamente) all'arresto di JVM.


Un hook di spegnimento è ingombrante. Il file # deleteOnExit non funzionerebbe anche?
Daniel Hiller,

2
#deleteOnExit non ha funzionato per me - credo che non cancellerà le directory non vuote.
Muriloq,

Ho implementato un test rapido in esecuzione con Java 8, ma la cartella temporanea non è stata eliminata, consultare pastebin.com/mjgG70KG
geri,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.