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