¿Cómo crear un directorio / carpeta temporal en Java?

Respuestas:

390

Si está utilizando JDK 7, use la nueva clase Files.createTempDirectory para crear el directorio temporal.

Path tempDirWithPrefix = Files.createTempDirectory(prefix);

Antes de JDK 7 esto debería hacerlo:

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

Podría hacer mejores excepciones (subclase IOException) si lo desea.

TofuBeer
fuente
12
Esto es peligroso. Java es conocido no para borrar archivos de forma inmediata, por lo que puede fallar a veces mkdir
Demiurg
44
@Demiurg El único caso de un archivo que no se elimina de inmediato es en Windows cuando el archivo ya está abierto (por ejemplo, podría abrirlo un antivirus). ¿Tiene otra documentación que mostrar de otra manera (tengo curiosidad por cosas como esa :-)? Si sucede con regularidad, el código anterior no funcionará, si es raro, entonces, la interrupción de la llamada al código anterior hasta que ocurra la eliminación (o se alcancen algunos intentos máximos) funcionaría.
TofuBeer
66
@Demiurg Java es conocido por no eliminar archivos de inmediato. Eso es cierto, incluso si no lo abres. Entonces, una forma más segura es temp.delete(); temp = new File(temp.getPath + ".d"); temp.mkdir(); ..., temp.delete();.
Xiè Jìléi
102
Este código tiene una condición de carrera entre delete()y mkdir(): Un proceso malicioso podría crear el directorio de destino mientras tanto (tomando el nombre del archivo creado recientemente). Ver Files.createTempDir()para una alternativa.
Joachim Sauer
11
Me gusta el ! para destacar, demasiado fácil para perdérselo. Leí mucho código escrito por estudiantes ... si (! I) es lo suficientemente común como para ser molesto :-)
TofuBeer
182

La biblioteca Google Guava tiene muchas utilidades útiles. Una de las notas aquí es la clase de archivos . Tiene un montón de métodos útiles que incluyen:

File myTempDir = Files.createTempDir();

Esto hace exactamente lo que pediste en una línea. Si lee la documentación aquí , verá que la adaptación propuesta File.createTempFile("install", "dir")generalmente introduce vulnerabilidades de seguridad.

Espina
fuente
Me pregunto a qué vulnerabilidad te refieres. Este enfoque no parece crear una condición de carrera, ya que se supone que File.mkdir () falla si dicho directorio ya existe (creado por un atacante). Tampoco creo que esta llamada siga a través de enlaces simbólicos maliciosos. ¿Podría aclarar lo que ha querido decir?
abb
3
@abb: No conozco los detalles de la condición de carrera que se menciona en la documentación de Guava. Sospecho que la documentación es correcta dado que específicamente señala el problema.
Spina
1
@abb Tienes razón. Mientras se verifique el retorno de mkdir (), sería seguro. El código que señala Spina utiliza este método mkdir (). grepcode.com/file/repo1.maven.org/maven2/com.google.guava/guava/… . Esto es solo un problema potencial en los sistemas Unix cuando se usa el directorio / tmp porque tiene habilitado el bit fijo.
Sarel Botha
@SarelBotha gracias por completar el espacio en blanco aquí. Me había estado preguntando ociosamente sobre esto por bastante tiempo.
Espina
168

Si necesita un directorio temporal para la prueba y está utilizando jUnit, @Rulejunto con TemporaryFolderresuelve su problema:

@Rule
public TemporaryFolder folder = new TemporaryFolder();

De la documentación :

La regla de TemporaryFolder permite la creación de archivos y carpetas que se garantiza que se eliminarán cuando finalice el método de prueba (ya sea que pase o no)


Actualizar:

Si está utilizando JUnit Jupiter (versión 5.1.1 o superior), tiene la opción de utilizar JUnit Pioneer, que es el paquete de extensión JUnit 5.

Copiado de la documentación del proyecto :

Por ejemplo, la siguiente prueba registra la extensión para un único método de prueba, crea y escribe un archivo en el directorio temporal y verifica su contenido.

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

Más información en JavaDoc y 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>

Actualización 2:

La anotación @TempDir se agregó a la versión JUnit Jupiter 5.4.0 como una característica experimental. Ejemplo copiado de la Guía del usuario 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));
}
matsev
fuente
8
Disponible desde JUnit 4.7
Eduard Wirch
¡No funciona en JUnit 4.8.2 en Windows 7! (Problema de permisos)
excepción
2
@CraigRinger: ¿Por qué no es prudente confiar en esto?
Adam Parkin
2
@ AdamParkin Honestamente, ya no me acuerdo. ¡La explicación falla!
Craig Ringer el
1
El principal beneficio de este enfoque es que JUnit administra el directorio (creado antes de la prueba y eliminado recursivamente después de la prueba). Y funciona. Si obtiene "directorio temporal aún no creado", podría deberse a que olvidó @Rule o el campo no es público.
Bogdan Calmac
42

El código escrito ingenuamente para resolver este problema adolece de condiciones de carrera, incluidas varias de las respuestas aquí. Históricamente, podría pensar detenidamente sobre las condiciones de carrera y escribirlo usted mismo, o podría usar una biblioteca de terceros como la guayaba de Google (como sugirió la respuesta de Spina). O podría escribir un código defectuoso.

Pero a partir de JDK 7, ¡hay buenas noticias! La biblioteca estándar de Java en sí misma ahora proporciona una solución que funciona correctamente (no rápida) a este problema. Desea java.nio.file.Files # createTempDirectory () . De la documentación :

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

Crea un nuevo directorio en el directorio especificado, utilizando el prefijo dado para generar su nombre. La ruta resultante está asociada con el mismo FileSystem que el directorio dado.

Los detalles sobre cómo se construye el nombre del directorio dependen de la implementación y, por lo tanto, no se especifican. Siempre que sea posible, el prefijo se usa para construir nombres de candidatos.

Esto resuelve efectivamente el vergonzosamente antiguo informe de error en el rastreador de errores de Sun que pedía tal función.

Greg Price
fuente
35

Este es el código fuente de Files.createTempDir () de la biblioteca de Guava. No es tan complejo como podría pensar:

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 defecto:

private static final int TEMP_DIR_ATTEMPTS = 10000;

Mira aquí

Andres Kievsky
fuente
28

No lo use deleteOnExit()incluso si lo elimina explícitamente más tarde.

Google 'deleteonexit es malo' para obtener más información, pero la esencia del problema es:

  1. deleteOnExit() solo se elimina en el caso de paradas normales de JVM, no se bloquea o mata el proceso de JVM.

  2. deleteOnExit() solo se elimina al cerrar JVM; no es bueno para procesos de servidor de larga ejecución porque:

  3. El más malvado de todos: deleteOnExit()consume memoria para cada entrada de archivo temporal. Si su proceso se ejecuta durante meses o crea muchos archivos temporales en poco tiempo, consume memoria y nunca la libera hasta que se apaga la JVM.

Desarrollador Dude
fuente
1
Tenemos una JVM donde los archivos de clase y jar obtienen archivos ocultos debajo creados por la JVM, y esta información adicional tarda bastante en eliminarse. Al realizar nuevas implementaciones en caliente en contenedores web que explotan WAR, la JVM puede tomar literalmente minutos para limpiar después de terminar, pero antes de salir cuando se ha ejecutado durante unas horas.
Thorbjørn Ravn Andersen
20

A partir de Java 1.7 createTempDirectory(prefix, attrs)y createTempDirectory(dir, prefix, attrs)están incluidos enjava.nio.file.Files

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

buscador
fuente
14

Esto es lo que decidí hacer para mi propio 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();
}
Keith
fuente
2
Esto es inseguro. Ver comentario de Joachim Sauer en la primera opción (igualmente insegura). La forma correcta de verificar la existencia de un archivo o directorio y luego tomar el nombre de archivo, atómicamente, es creando el archivo o directorio.
zbyszek
1
@zbyszek javadocs dice "El UUID se genera utilizando un generador de números pseudoaleatorios criptográficamente fuerte". Dado que un proceso malicioso crea un directorio con el mismo nombre entre exist () y mkdirs (). De hecho, al ver esto ahora, creo que mi prueba existe () podría ser un poco tonta.
Keith
Keith: UUID ser seguro o no no es crucial en este caso. Es suficiente para que la información sobre el nombre que ha consultado de alguna manera "escape". Por ejemplo, supongamos que el archivo que se está creando está en un sistema de archivos NFS, y el atacante puede escuchar (pasivamente) los paquetes. O el estado del generador aleatorio se ha filtrado. En mi comentario dije que su solución es igualmente insegura como la respuesta aceptada, pero esto no es justo: la aceptada es trivial para vencer con inotify, y esta es mucho más difícil de vencer. Sin embargo, en algunos escenarios es ciertamente posible.
zbyszek
2
Pensé lo mismo e implementé una solución usando UUID aleatorios como este. No existe ninguna verificación, solo un intento de crear: el RNG fuerte utilizado por el método randomUUID prácticamente no garantiza colisiones (puede usarse para generar claves primarias en las tablas de base de datos, lo hice yo mismo y nunca conocí una colisión), por lo que es bastante seguro. Si alguien no está seguro, visite stackoverflow.com/questions/2513573/…
brabster
Si observa la implementación de Java, solo generan nombres aleatorios hasta que no haya colisión. Sus intentos máximos son infinitos. Entonces, si alguien malicioso siguiera adivinando su nombre de archivo / directorio, se repetiría para siempre. Aquí hay un enlace a la fuente: hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/9fb81d7a2f16/src/share/ ... Estaba pensando que de alguna manera podría bloquear el sistema de archivos para generar atómicamente un nombre único y crear el directorio, pero supongo que no lo hace de acuerdo con el código fuente.
dosentmatter
5

Bueno, "createTempFile" realmente crea el archivo. Entonces, ¿por qué no simplemente eliminarlo primero y luego hacer el mkdir en él?

Paul Tomblin
fuente
1
Siempre debe verificar el valor de retorno para mkdir (). Si eso es falso, significa que el directorio ya existía. Esto puede causar problemas de seguridad, así que considere si esto debería generar un error en su aplicación o no.
Sarel Botha
1
Vea la nota sobre la condición de la carrera en la otra respuesta.
Volker Stolz
Esto me gusta, salvo la carrera
Martin Wickman
4

Este código debería funcionar razonablemente 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;
}
mate b
fuente
3
¿Qué sucede si el directorio ya existe y usted no tiene acceso de lectura / escritura o qué pasa si es un archivo normal? También tienes una condición de carrera allí.
Jeremy Huiskamp
2
Además, deleteOnExit no eliminará los directorios no vacíos.
Trenton
3

Como se discutió en este RFE y sus comentarios, puede llamar tempDir.delete()primero. O podría usar System.getProperty("java.io.tmpdir")y crear un directorio allí. De cualquier manera, debe recordar llamartempDir.deleteOnExit() , o el archivo no se eliminará una vez que haya terminado.

Michael Myers
fuente
¿No se llama esta propiedad "java.io.tmpdir", no "... temp"? Ver java.sun.com/j2se/1.4.2/docs/api/java/io/File.html
Andrew Swan el
Muy asi. Debería haber verificado antes de repetir lo que leí.
Michael Myers
El java.io.tmpdir se comparte, por lo que debe hacer todo el vudú habitual para evitar pisar los dedos de los pies de otra persona.
Thorbjørn Ravn Andersen
3

Solo para completar, este es el código de la biblioteca google guava. No es mi código, pero creo que es valioso mostrarlo aquí en este hilo.

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

Tengo el mismo problema, así que esta es solo otra respuesta para aquellos que están interesados, y es similar a uno de los anteriores:

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

Y para mi aplicación, decidí agregar una opción para borrar la temperatura al salir, así que agregué en un gancho de apagado:

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

El método elimina todos los subdirectorios y archivos antes de eliminar la temperatura , sin usar la pila de llamadas (que es totalmente opcional y podría hacerlo con recursividad en este punto), pero quiero estar seguro.

inmóvil
fuente
2

Como puede ver en las otras respuestas, no ha surgido un enfoque estándar. Por lo tanto, ya mencionó Apache Commons, propongo el siguiente enfoque utilizando FileUtils 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;

}

Esto se prefiere ya que apache commons es la biblioteca que se acerca más al "estándar" solicitado y funciona tanto con JDK 7 como con versiones anteriores. Esto también devuelve una instancia de archivo "antigua" (que está basada en transmisión) y no una instancia de ruta "nueva" (que está basada en búfer y sería el resultado del método getTemporaryDirectory () de JDK7) -> Por lo tanto, devuelve lo que la mayoría de la gente necesita cuando Quieren crear un directorio temporal.

Carsten Engelke
fuente
1

Me gustan los múltiples intentos de crear un nombre único, pero incluso esta solución no descarta una condición de carrera. Se puede introducir otro proceso después de la prueba exists()y la if(newTempDir.mkdirs())invocación del método. No tengo idea de cómo hacer que esto sea completamente seguro sin recurrir al código nativo, que supongo es lo que está enterrado dentro File.createTempFile().

Chris Lott
fuente
1

Antes de Java 7 también podría:

File folder = File.createTempFile("testFileUtils", ""); // no suffix
folder.delete();
folder.mkdirs();
folder.deleteOnExit();
geri
fuente
1
Buen código Pero desafortunadamente "deleteOnExit ()" no funcionará, ya que Java no puede eliminar toda la carpeta a la vez. Tienes que eliminar todos los archivos de forma recursiva: /
Adam Taras
1

Prueba este pequeño ejemplo:

Código:

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


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

Salida de consola en máquina Windows:
C: \ Users \ userName \ AppData \ Local \ Temp \ tmpDir2908538301081367877

Comentario:
Files.createTempDirectory genera una ID única atómicamente: 2908538301081367877.

Nota:
lea lo siguiente para eliminar directorios de forma recursiva: elimine
directorios de forma recursiva en Java

DigitShifter
fuente
0

Usar File#createTempFiley deletepara crear un nombre único para el directorio parece estar bien. Debe agregar a ShutdownHookpara eliminar el directorio (recursivamente) en el apagado de JVM.

ordnungswidrig
fuente
Un gancho de cierre es engorroso. ¿No funcionaría también File # deleteOnExit?
Daniel Hiller
2
#deleteOnExit no funcionó para mí: creo que no eliminará los directorios no vacíos.
muriloq
Implementé una prueba rápida con Java 8, pero la carpeta temporal no se eliminó, consulte pastebin.com/mjgG70KG
geri