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

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

Ответы

Ответ 1

Если вы используете 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), если хотите.

Ответ 2

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

File myTempDir = Files.createTempDir();

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

Ответ 3

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

@Rule
public TemporaryFolder folder = new TemporaryFolder();

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

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


Обновить:

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

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

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

@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</scope>
</dependency>

Ответ 4

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

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

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

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

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

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

Ответ 5

Это исходный код библиотеки 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;

Смотрите здесь

Ответ 6

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

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

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

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

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

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

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

Ответ 11

Просто для завершения, это код из 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 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)
            + ')');
  }

Ответ 12

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

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

Ответ 13

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

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

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

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 (который является полностью необязательным, и вы можете сделать это с рекурсией на этом этапе), но я хочу быть на безопасная сторона.

Ответ 14

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

Ответ 15

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

Ответ 16

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

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

Ответ 17

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