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


Answers:


390

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


12
这很危险。已知Java不会立即删除文件,因此mkdir有时可能会失败
Demiurg 2010年

4
@Demiurg唯一不立即删除文件的情况是在Windows中已经打开文件(例如,它可以由病毒扫描程序打开)。您是否还有其他文档需要显示(我对这样的事情感到好奇:-)?如果它定期发生,那么上面的代码将无法正常工作,如果很少,则将对上面代码的调用放到发生删除操作(或达到最大尝试次数)之前。
TofuBeer

6
@Demiurg Java不会立即删除文件。没错,即使您不打开它也是如此。因此,一种更安全的方法是temp.delete(); temp = new File(temp.getPath + ".d"); temp.mkdir(); ..., temp.delete();
耶吉莱2011年

102
这段代码的竞争条件是delete()和之间mkdir():恶意进程可能同时创建目标目录(采用最近创建的文件的名称)。请参阅另Files.createTempDir()一种方法。
Joachim Sauer

11
我喜欢 !脱颖而出,太容易错过了。我读了很多学生写的代码... if(!i)很常见,很烦人:-)
TofuBeer 2011年

182

Google Guava库具有大量有用的实用程序。这里要注意的一个是Files类。它有很多有用的方法,包括:

File myTempDir = Files.createTempDir();

这完全符合您的要求。如果您在此处阅读文档将会看到建议的改编File.createTempFile("install", "dir")通常会引入安全漏洞。


我想知道您指的是什么漏洞。这种方法似乎不会创建竞争条件,因为如果该目录已经存在(由攻击者创建),则File.mkdir()可能会失败。我认为此呼叫也不会通过恶意符号链接进行。您能说明您的意思吗?
ABB

3
@abb:我不知道Guava文档中提到的比赛条件的详细信息。我怀疑该文档是正确的,因为它明确指出了问题所在。
斯宾娜

1
@abb你是对的。只要检查了mkdir()的返回值,它就是安全的。代码Spina指向使用此mkdir()方法。grepcode.com/file/repo1.maven.org/maven2/com.google.guava/guava/…。使用/ tmp目录时,这仅是在Unix系统上的潜在问题,因为它启用了粘滞位。
Sarel Botha

@SarelBotha感谢您在此处填写空白。我一直在对此感到无所适从。
斯宾娜2013年

168

如果您需要一个临时目录进行测试并且正在使用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);
}

JavaDocTempDirectoryJavaDoc中的更多信息

摇篮:

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的木星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));
}

8
自JUnit 4.7
Eduard Wirch 2011年

在Windows 7的JUnit 4.8.2中不起作用!(权限问题)
例外情况

2
@CraigRinger:为什么依靠它是不明智的?
亚当·帕金

2
@AdamParkin老实说,我已经不记得了。说明失败!
Craig Ringer 2013年

1
这种方法的主要好处是目录由JUnit管理(在测试之前创建,在测试之后递归删除)。它确实有效。如果收到“尚未创建临时目录”,则可能是因为您忘记了@Rule或未公开的字段。
Bogdan Calmac '16

42

用于解决此问题的幼稚代码受竞争条件的影响,其中包括此处的几个答案。从历史上看,您可以仔细考虑比赛条件并自己编写,或者可以使用Google的Guava之类的第三方库(如Spina的答案所建议。),或者可以编写错误的代码。

但是从JDK 7开始,有个好消息!Java标准库本身现在提供了解决此问题的正确方法(非民主)。您需要java.nio.file.Files#createTempDirectory()。从文档中

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

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

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

这有效地解决了Sun bug跟踪器中令人尴尬的古老bug报告,该报告只需要这样的功能。


35

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

看这里


28

deleteOnExit()即使以后明确删除它也不要使用。

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

  1. deleteOnExit() 仅在正常JVM关闭时删除,而不会崩溃或杀死JVM进程。

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

  3. 最邪恶的- deleteOnExit()占用每个临时文件条目的内存。如果您的进程运行了几个月,或者在很短的时间内创建了许多临时文件,则您将消耗内存,并且在JVM关闭之前永远不要释放内存。


1
我们有一个JVM,其中类和jar文件在JVM创建的下方获取隐藏文件,并且这些额外的信息需要相当长的时间才能删除。在爆炸WAR的Web容器上进行热重新部署时,JVM从字面上看可能需要花几分钟才能清理完毕,但要运行几个小时才能退出。
托尔比约恩Ravn的安徒生

20

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

例: File tempDir = Files.createTempDirectory("foobar").toFile();


14

这是我决定为自己的代码执行的操作:

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

2
这是不安全的。请参阅Joachim Sauer在第一个(同样不安全)选项中的评论。从原子上来说,检查文件或目录是否存在以及获取文件名的正确方法是创建文件或目录。
zbyszek 2012年

1
@zbyszek javadocs说:“ UUID是使用强密码学的伪随机数生成器生成的。” 鉴于恶意进程如何创建在exist()和mkdirs()之间具有相同名称的目录。实际上,现在看来,我认为我的exist()测试可能有点傻。
基思(Keith)

Keith:在这种情况下,UUID是否安全并不重要。有关您以某种方式“泄漏”查询的名称的信息就足够了。例如,假设正在创建的文件位于NFS文件系统上,攻击者可以(被动地)侦听数据包。或随机生成器状态已泄漏。在我的评论中,我说您的解决方案与接受的答案同样不安全,但这是不公平的:被接受的解决方案很难被inotify击败,而这一解决方案则很难克服。但是,在某些情况下,这确实是可能的。
zbyszek 2012年

2
我有同样的想法,并使用像这样的随机UUID实现了一个解决方案。不检查是否存在,只有一种尝试创建-randomUUID方法使用的强RNG几乎可以保证没有冲突(可以用于在DB表中生成主键,这是我自己做的,并且从未发生冲突),因此非常有信心。如果任何人不知道,看看stackoverflow.com/questions/2513573/...
brabster

如果看一下Java的实现,它们只会生成随机名称,直到没有冲突为止。他们的最大尝试是无限的。因此,如果有人恶意猜测您的文件/目录名称,它将永远循环。这是到源的链接:hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/9fb81d7a2f16/src/share / ...我在想,它可以某种方式锁定文件系统,从而可以自动生成唯一的名称并创建目录,但是根据源代码,我想它不会这样做。
剂量ntmatter

5

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


1
您应该始终检查mkdir()的返回值。如果为假,则表示该目录已存在。这可能会导致安全问题,因此请考虑这是否会在您的应用程序中引发错误。
Sarel Botha

1
请参阅其他答案中有关竞赛条件的注释。
Volker Stolz 2013年

这个我喜欢,除非比赛
马丁威克曼

4

该代码应该可以正常工作:

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

3
如果目录已经存在并且您没有对该目录的读/写权限,或者该目录是常规文件,该怎么办?您在那里也有比赛条件。
Jeremy Huiskamp,2009年

2
同样,deleteOnExit不会删除非空目录。
特伦顿

3

本RFE及其评论所述,您可以tempDir.delete()先致电。或者,您可以在其中使用System.getProperty("java.io.tmpdir")并创建目录。无论哪种方式,您都应该记住调用tempDir.deleteOnExit(),否则操作完成后该文件将不会被删除。


这个属性不是叫做“ java.io.tmpdir”,不是“ ... temp”吗?参见java.sun.com/j2se/1.4.2/docs/api/java/io/File.html
Andrew Swan

这么。在重复阅读之前,我应该已经验证。
迈克尔·迈尔斯

java.io.tmpdir是共享的,因此您需要执行所有常用的伏都教义,以避免踩到别人的脚趾。
托尔比约恩Ravn的安徒生

3

只是为了完成,这是来自Google番石榴库的代码。这不是我的代码,但我认为在此线程中显示它很有价值。

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

2

我遇到了同样的问题,所以这只是对那些有兴趣的人的另一个答案,它与上述之一相似:

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

该方法在删除temp之前先删除所有子目录和文件,而不使用调用栈(这是完全可选的,此时您可以使用递归进行此操作),但是我想保持安全。


2

正如您在其他答案中看到的那样,没有标准的方法出现。因此,您已经提到了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 commons是最接近要求的“标准”的库,并且可与JDK 7和更早版本一起使用。这还会返回一个“旧” File实例(基于流),而不是一个“ new” Path实例(基于缓冲区,这是JDK7的getTemporaryDirectory()方法的结果)->因此,它返回大多数人在需要时他们想创建一个临时目录。


1

我喜欢创建唯一名称的多次尝试,但是即使这种解决方案也不能排除竞争条件。测试exists()if(newTempDir.mkdirs())方法调用后,另一个进程可能会进入。我不知道如何在不依靠本机代码的情况下完全确保这一点的安全,我想这是埋在里面的File.createTempFile()


1

在Java 7之前,您还可以:

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

1
好的代码。但是不幸的是,“ deleteOnExit()”将不起作用,因为Java无法一次删除整个文件夹。您必须递归删除所有文件:/
Adam Taras

1

试试这个小例子:

码:

try {
    Path tmpDir = Files.createTempDirectory("tmpDir");
    System.out.println(tmpDir.toString());
    Files.delete(tmpDir);
} catch (IOException e) {
    e.printStackTrace();
}


导入:
java.io.IOException
java.nio.file.Files
java.nio.file.Path

Windows计算机上的控制台输出:
C:\ Users \ userName \ AppData \ Local \ Temp \ tmpDir2908538301081367877

注释:
Files.createTempDirectory原子地生成唯一ID-2908538301081367877。

注意:
阅读以下内容以递归方式删除目录在Java中以递归方式
删除目录


0

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


关闭挂钩很麻烦。File#deleteOnExit也不能正常工作吗?
Daniel Hiller

2
#deleteOnExit对我不起作用-我相信它不会删除非空目录。
muriloq,2009年

我实现了使用Java 8运行的快速测试,但未删除临时文件夹,请参见pastebin.com/mjgG70KG
geri
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.