Как создать временный каталог / папку в Java?

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

17 ответов

Решение

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

Path tempDirWithPrefix = Files.createTempDirectory(prefix);

Перед 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 есть множество полезных утилит. Одним из примечаний здесь является класс Files. У него есть куча полезных методов, в том числе:

File myTempDir = Files.createTempDir();

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

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

@Rule
public TemporaryFolder folder = new TemporaryFolder();

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

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


Обновить:

Если вы используете JUnit Jupiter (версия 5.1.1 или выше), у вас есть возможность использовать JUnit Pioneer, который является JUnit 5 Extension Pack.

Скопировано из проектной документации:

Например, следующий тест регистрирует расширение для одного метода теста, создает и записывает файл во временный каталог и проверяет его содержимое.

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

Больше информации в JavaDoc и JavaDoc 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</test>
</dependency>

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

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

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

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

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

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

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

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, а не для сбоев или уничтожения процесса 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?

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

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

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

Просто для завершения, это код из библиотеки Google Guava. Это не мой код, но я думаю, что стоит показать его здесь, в этой теме.

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

Попробуйте этот небольшой пример:

Код:

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


Импорт:
java.io.IOException
java.nio.file.Files
java.nio.file.Path

Вывод в консоль на компьютере с Windows:
C:\Users\userName\AppData\Local\Temp\tmpDir2908538301081367877

Комментарий:
Files.createTempDirectory генерирует уникальный идентификатор автоматически - 2908538301081367877.

Примечание.
Для рекурсивного удаления каталогов прочтите следующее:
Удаляйте каталоги рекурсивно в Java.

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

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

Этот метод удаляет все подкаталоги и файлы перед удалением temp без использования callstack (что совершенно необязательно, и вы можете сделать это с помощью рекурсии на этом этапе), но я хочу быть на безопасной стороне.

Как видно из других ответов, стандартного подхода не возникло. Следовательно, вы уже упоминали 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, так и с более старыми версиями. Это также возвращает "старый" экземпляр File (основанный на потоке), а не "новый" экземпляр Path (основанный на буфере и являющийся результатом метода getDeventDirectory ()) JDK7 -> Поэтому он возвращает то, что нужно большинству людей, когда они хотят создать временный каталог.

До Java 7 вы также могли:

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

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

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

Другие вопросы по тегам