Как создать временную папку / папку в Java?


Существует ли стандартный и надежный способ создания временного каталога внутри приложения Java? В базе данных проблем Java есть запись, в которой есть код кода, но мне интересно, есть ли стандартное решение, которое можно найти в одной из обычных библиотек (Apache Commons и т. Д.)?


Answers



Если вы используете JDK 7, используйте новый класс Files.createTempDirectory для создания временного каталога.

Перед JDK 7 это должно сделать это:

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

Вы можете сделать лучшие исключения (подкласс IOException), если хотите.




В библиотеке Google Guava имеется множество полезных утилит. Здесь следует отметить класс «Файлы» . Он содержит множество полезных методов, в том числе:

File myTempDir = Files.createTempDir();

Это делает именно то, что вы просили в одной строке. Если вы прочитаете здесь документацию, вы увидите, что предлагаемая адаптация File.createTempFile("install", "dir") обычно вводит уязвимости безопасности.




Если вам нужен временный каталог для тестирования, и вы используете jUnit, @Rule вместе с TemporaryFolder решает вашу проблему:

@Rule
public TemporaryFolder folder = new TemporaryFolder();

Из документации:

Правило TemporaryFolder позволяет создавать файлы и папки, которые гарантированно будут удалены при завершении метода тестирования (независимо от того, проходит он или нет)

Ссылка: http://junit.org/apidocs/org/junit/rules/TemporaryFolder.html




Наивно написанный код для решения этой проблемы страдает от условий гонки, включая несколько ответов здесь. Исторически вы могли бы тщательно подумать о состоянии гонки и написать ее самостоятельно, или вы могли бы использовать стороннюю библиотеку, такую ​​как Guava Google (как предложил ответ Spina). Или вы можете написать багги-код.

Но с JDK 7 есть хорошие новости! Сама стандартная библиотека Java теперь обеспечивает надлежащим образом работающее (нерациональное) решение этой проблемы. Вы хотите java.nio.file.Files # createTempDirectory () . Из документации :

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

Создает новый каталог в указанном каталоге, используя префикс для генерации его имени. Полученный Путь связан с той же FileSystem, что и указанный каталог.

Информация о том, как создается имя каталога, зависит от реализации и поэтому не указана. По возможности префикс используется для создания имен кандидатов.

Это эффективно устраняет неловко древний отчет об ошибках в трекер Sun, который попросил именно такую ​​функцию.




Это исходный код для файлов библиотеки Guava Files.createTempDir (). Это не так сложно, как вы думаете:

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

По умолчанию:

private static final int TEMP_DIR_ATTEMPTS = 10000;

Глянь сюда




Не используйте deleteOnExit() даже если вы явно удалите его позже.

Google «deleteonexit is evil» для получения дополнительной информации, но суть проблемы заключается в следующем:

  1. deleteOnExit() удаляет только для нормального отключения JVM, а не deleteOnExit() или уничтожению процесса JVM.

  2. deleteOnExit() удаляет только при отключении JVM - не подходит для длительных серверных процессов, потому что:

  3. Самое злое из всех - deleteOnExit() потребляет память для каждой записи временного файла. Если ваш процесс работает в течение нескольких месяцев или создает много временных файлов за короткое время, вы потребляете память и никогда не отпускаете ее до тех пор, пока JVM не выключится.




Начиная с Java 1.7 createTempDirectory(prefix, attrs) и createTempDirectory(dir, prefix, attrs) включены в java.nio.file.Files

Пример: File tempDir = Files.createTempDirectory("foobar").toFile();




Это то, что я решил сделать для своего собственного кода:

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



Ну, «createTempFile» фактически создает файл. Так почему бы просто не удалить его сначала, а затем сделать mkdir на нем?




Как обсуждалось в этом RFE и его комментариях, вы можете tempDir.delete() вызвать tempDir.delete() . Или вы можете использовать System.getProperty("java.io.tmpdir") и создать там каталог. В любом случае, вы должны помнить, что вы вызываете tempDir.deleteOnExit() , иначе файл не будет удален после завершения.




У меня такая же проблема, так что это просто еще один ответ для тех, кто интересуется, и это похоже на одно из вышеперечисленных:

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

И для моего приложения я решил, что добавить опцию очистки временной задержки на выходе, чтобы я добавил в крючок отключения:

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

Метод удаляет все поддиры и файлы перед удалением темпа, не используя столбец (который является полностью необязательным, и вы можете сделать это с рекурсией на данном этапе), но я хочу быть в безопасности.




Для завершения это код из библиотеки google goava. Это не мой код, но я считаю его полезным показать здесь в этой теме.

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



Этот код должен работать достаточно хорошо:

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



Мне нравится несколько попыток создания уникального имени, но даже это решение не исключает условия гонки. Другой процесс может проскальзывать после теста для exists() и вызова метода if(newTempDir.mkdirs()) . Я не знаю, как полностью сделать это безопасным, не прибегая к собственному коду, который, как я полагаю, является тем, что File.createTempFile() внутри File.createTempFile() .




Как вы можете видеть в других ответах, никакого стандартного подхода не возникло. Следовательно, вы уже упоминали Apache Commons, я предлагаю следующий подход с использованием FileUtils от 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;

}

Это предпочтительнее, поскольку apache использует библиотеку, которая ближе всего подходит к заданному «стандарту» и работает как с JDK 7, так и более старыми версиями. Это также возвращает «старый» экземпляр файла (который основан на потоке), а не «новый» экземпляр Path (который основан на буфере и будет результатом метода JDK7 getTemporaryDirectory ()) -> Поэтому он возвращает то, что нужно большинству людей, когда они хотят создать временную директорию.




Перед Java 7 вы также можете:

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



Использование File#createTempFile и delete для создания уникального имени для каталога выглядит нормально. Вы должны добавить ShutdownHook для удаления каталога (рекурсивно) при отключении JVM.