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



Answers

La biblioteca de Google Guava tiene toneladas de 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 de File.createTempFile("install", "dir") generalmente presenta vulnerabilidades de seguridad.

Question

¿Existe una forma estándar y confiable de crear un directorio temporal dentro de una aplicación Java? Hay una entrada en la base de datos de problemas de Java , que tiene un poco de código en los comentarios, pero me pregunto si hay una solución estándar que se encuentre en una de las bibliotecas habituales (Apache Commons, etc.).




Solo para completar, este es el código de la biblioteca de 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)
            + ')');
  }



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




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

Pero a partir de JDK 7, ¡hay buenas noticias! La biblioteca estándar de Java ahora proporciona una solución que funciona correctamente (no agresiva) para 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, usando el prefijo dado para generar su nombre. La ruta de acceso resultante está asociada con el mismo sistema de archivos 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 están especificados. Donde sea posible, el prefijo se usa para construir nombres de candidatos.

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




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

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

  1. deleteOnExit() solo se elimina para las paradas normales de JVM, no bloquea o elimina el proceso de JVM.

  2. deleteOnExit() solo se elimina en el cierre de JVM, lo que no es bueno para los procesos de servidor de ejecución prolongada 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 lo suelta hasta que la JVM se apaga.




Antes de Java 7 también podías:

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



Esto es lo que decidí hacer por 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();
}



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




Links