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

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

17 ответов


Если вы используете JDK 7, Используйте new файлы.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 имеет массу полезных утилит. Одним из примечательных здесь является класс. Он имеет кучу полезных методов, включая:

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.

скопировал из проектная документация:

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

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

подробнее на JavaDoc и документация на Каталога Temp

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 (как предложил ответ спины.) Или вы можете написать багги-код.

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

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

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

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

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


это исходный код файлов библиотеки Guava.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-это зло" для получения дополнительной информации, но суть проблемы в следующем:

  1. deleteOnExit() удаляет только для обычных остановок JVM, а не сбоев или убийства процесса JVM.

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

  3. самое злое из всех -deleteOnExit() потребляет память для каждой записи temp-файла. Если ваш процесс выполняется в течение нескольких месяцев или создает много временных файлов за короткое время, вы потребляете память и никогда не освобождаете ее, пока 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() первый. Или вы можете использовать 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)
            + ')');
  }

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

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 на выходе, поэтому я добавил в крюк выключения:

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


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

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().


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


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

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

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