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