如何在Java中创建一个临时目录/文件夹?

是否有一种在Java应用程序中创建临时目录的标准而可靠的方法?有Java的问题数据库中的一个条目,它在注释中有一些代码,但我想知道是否有一个标准的解决方案可以在一个常用的库(Apache Commons等)中找到?

452119 次浏览

createTempFile实际上创建了文件。那么为什么不先删除它,然后再对它执行mkdir呢?

正如这RFE及其注释中所讨论的,可以先调用tempDir.delete()。或者你可以使用System.getProperty("java.io.tmpdir")在那里创建一个目录。无论哪种方式,你都应该记得调用tempDir.deleteOnExit(),否则在你完成之后文件将不会被删除。

这段代码应该可以很好地工作:

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

如果你使用的是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)。

使用File#createTempFiledelete为目录创建一个唯一的名称似乎是可以的。您应该添加ShutdownHook来在JVM关闭时(递归地)删除目录。

这是我决定为我自己的代码所做的:

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

不要使用deleteOnExit(),即使你以后显式地删除它。

谷歌“deleteonexit是邪恶的”获取更多信息,但问题的要点是:

  1. deleteOnExit()只删除正常的JVM关闭,而不是崩溃或杀死JVM进程。

  2. deleteOnExit()只在JVM关闭时删除-不适合长时间运行的服务器进程,因为:

  3. 最糟糕的是——deleteOnExit()为每个临时文件条目消耗内存。如果您的进程运行了几个月,或者在短时间内创建了大量临时文件,那么您将消耗内存,直到JVM关闭才释放内存。

我喜欢创建唯一名称的多次尝试,但即使是这种解决方案也不排除竞争条件。另一个进程可以在exists()if(newTempDir.mkdirs())方法调用测试之后插入。我不知道如何在不求助于本机代码的情况下完全使此安全,我认为这是埋在File.createTempFile()中的内容。

如果你需要一个临时目录进行测试,并且你正在使用jUnit, @RuleTemporaryFolder可以解决你的问题:

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

JavaDocTempDirectory的JavaDoc

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>

更新2:

@TempDir注释作为实验特性被添加到JUnit Jupiter 5.4.0发行版中。从JUnit 5用户指南复制的示例:

@Test
void writeItemsToFile(@TempDir Path tempDir) throws IOException {
Path file = tempDir.resolve("test.txt");


new ListWriter(file).write("a", "b", "c");


assertEquals(singletonList("a,b,c"), Files.readAllLines(file));
}

谷歌Guava库有大量有用的实用程序。这里需要注意的是文件类。它有很多有用的方法,包括:

File myTempDir = Files.createTempDir();

这正是您在一行中要求的。如果你阅读文档在这里,你会发现File.createTempFile("install", "dir")的建议改编通常会引入安全漏洞。

为解决这个问题而编写的天真代码会受到竞争条件的影响,包括这里的几个答案。从历史上看,您可以仔细考虑竞争条件并自己编写它,或者您可以使用第三方库,如谷歌的Guava(正如Spina的回答所建议的那样)。或者你可以编写有bug的代码。

但是对于JDK 7,有一个好消息!Java标准库本身现在为这个问题提供了一个正常工作的(非刺激性的)解决方案。你需要java.nio.file.Files # createTempDirectory ()。从文档:

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

在指定目录中创建一个新目录,使用给定的前缀生成其名称。生成的Path与给定目录的相同文件系统相关联。

关于如何构造目录名称的详细信息取决于实现,因此没有指定。在可能的情况下,前缀用于构造候选名称。

这有效地解决了Sun错误跟踪器中要求这样一个函数的令人尴尬的古老bug报告

从Java 1.7开始,createTempDirectory(prefix, attrs)createTempDirectory(dir, prefix, attrs)包含在java.nio.file.Files

< p >的例子: File tempDir = Files.createTempDirectory("foobar").toFile(); < / p >

这是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;

See here . See here

我也遇到了同样的问题,所以这只是给那些感兴趣的人的另一个答案,它类似于上面的一个:

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

该方法在删除临时之前删除所有子dirs和文件,而不使用callstack(这完全是可选的,在这一点上你可以用递归来做),但我想安全起见。

正如你在其他答案中看到的,没有标准的方法出现。 因此,你已经提到了Apache Commons,我建议使用Apache Commons IO中的FileUtils来实现以下方法
/**
* 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和旧版本。这也返回了一个“旧的”文件实例(基于流),而不是一个“新的”路径实例(基于缓冲区,将是JDK7的getTemporaryDirectory()方法的结果)->因此,当大多数人想要创建临时目录时,它返回了他们需要的东西。

只是为了完成,这是谷歌番石榴库的代码。这不是我的代码,但我认为在这个线程中展示它是有价值的。

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

在Java 7之前,你还可以:

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

试试这个小例子: < br >

代码:

try {
Path tmpDir = Files.createTempDirectory("tmpDir");
System.out.println(tmpDir.toString());
Files.delete(tmpDir);
} catch (IOException e) {
e.printStackTrace();
}
< p > < br > 进口: < br > java.io.IOException < br > java.nio.file.Files < br > java.nio.file.Path < br > < / p > < p > Windows机器上的控制台输出: < br > C:\Users\userName\AppData\Local\Temp\ tmpDir2908538301081367877 < / p > < p > 备注: < br > 文件。createTempDirectory自动生成唯一ID - 2908538301081367877。< br >
注意: < br > 递归删除目录:
在Java中递归地删除目录 < / p >