是否有一种在Java应用程序中创建临时目录的标准而可靠的方法?在Java的问题数据库中有一个条目,在评论中有一些代码,但我想知道在一个常用的库(Apache Commons等)中是否有一个标准的解决方案?
当前回答
为解决这个问题而编写的天真代码会受到竞争条件的影响,包括这里的几个答案。从历史上看,您可以仔细考虑竞争条件并自己编写它,或者您可以使用第三方库,如谷歌的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跟踪器中要求这样一个功能的令人尴尬的古老bug报告。
其他回答
只是为了完成,这是谷歌番石榴库的代码。这不是我的代码,但我认为在这个线程中展示它是有价值的。
/** 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)
+ ')');
}
如果你需要一个临时目录进行测试,并且你正在使用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和TempDirectory的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));
}
我喜欢创建唯一名称的多次尝试,但即使是这种解决方案也不排除竞争条件。另一个进程可以在exists()和if(newTempDir.mkdirs())方法调用测试之后插入。我不知道如何在不求助于本机代码的情况下完全使其安全,我认为这是隐藏在File.createTempFile()中的内容。
即使稍后显式删除它,也不要使用deleteOnExit()。
谷歌'deleteonexit is evil'获取更多信息,但问题的要点是:
deleteOnExit()只删除正常的JVM关闭,不删除崩溃或杀死JVM进程。 deleteOnExit()只在JVM关闭时删除-不适合长时间运行的服务器进程,因为: 最糟糕的是deleteOnExit()为每个临时文件条目消耗内存。如果您的进程运行了几个月,或者在短时间内创建了大量临时文件,那么您将消耗内存,直到JVM关闭才释放内存。
如果您使用的是JDK 7,请使用新的Files。类创建临时目录。
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)。
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- 如何在Python中获得所有直接子目录
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder