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

时间:2009-03-06 01:17:55

标签: java file file-io directory temporary-directory

在Java应用程序中是否有标准且可靠的方法来创建临时目录?有an entry in Java's issue database,在评论中有一些代码,但我想知道是否有一个标准的解决方案可以在其中一个常见的库(Apache Commons等)中找到?

18 个答案:

答案 0 :(得分:347)

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

答案 1 :(得分:178)

Google Guava库有很多实用工具。其中一个值得注意的是Files class。它有许多有用的方法,包括:

File myTempDir = Files.createTempDir();

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

答案 2 :(得分:153)

如果您需要一个临时目录进行测试并使用jUnit,@RuleTemporaryFolder一起解决了您的问题:

@Rule
public TemporaryFolder folder = new TemporaryFolder();

来自documentation

  

TemporaryFolder规则允许创建在测试方法完成时保证被删除的文件和文件夹(无论是通过还是失败)


更新:

如果您使用的是JUnit Jupiter(版本5.1.1或更高版本),则可以选择使用JUnit Pioneer,它是JUnit 5 Extension Pack。

project documentation

复制
  

例如,以下测试为单个测试方法注册扩展,创建文件并将其写入临时目录并检查其内容。

@Test
@ExtendWith(TempDirectory.class)
void test(@TempDir Path tempDir) {
    Path file = tempDir.resolve("test.txt");
    writeFile(file);
    assertExpectedFileContent(file);
}

JavaDocJavaDoc of TempDirectory

中的更多信息

摇篮:

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 User Guide复制的示例:

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

答案 3 :(得分:39)

用于解决此问题的天真编写的代码会受到竞争条件的影响,包括此处的几个答案。从历史上看,您可以仔细考虑竞争条件并自行编写,或者您可以使用像Google的Guava这样的第三方库(如Spina的回答所示。)或者您可以编写错误的代码。

但是从JDK 7开始,有好消息! Java标准库本身现在为这个问题提供了一个正常工作(非生动)的解决方案。你想要java.nio.file.Files#createTempDirectory()。来自documentation

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

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

     

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

这有效地解决了Sun bug跟踪器中的embarrassingly ancient bug report,它追求了这样的功能。

答案 4 :(得分:34)

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

答案 5 :(得分:25)

即使您稍后明确删除它,也不要使用deleteOnExit()

Google 'deleteonexit is evil'了解更多信息,但问题的要点是:

  1. deleteOnExit()仅删除正常的JVM关闭,而不是崩溃或终止JVM进程。

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

  3. 最恶劣的是 - deleteOnExit()为每个临时文件条目消耗内存。如果您的进程运行了几个月,或者在短时间内创建了大量临时文件,则会消耗内存,并且在JVM关闭之前永远不会释放它。

答案 6 :(得分:20)

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

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

答案 7 :(得分: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();
}

答案 8 :(得分:5)

嗯,“createTempFile”实际上是创建文件。那么为什么不先删除它,然后在上面执行mkdir呢?

答案 9 :(得分:3)

this RFE及其评论中所述,您可以先致电tempDir.delete()。或者你可以使用System.getProperty("java.io.tmpdir")并在那里创建一个目录。无论哪种方式,您都应该记得拨打tempDir.deleteOnExit(),或者在完成后不会删除该文件。

答案 10 :(得分:3)

刚刚完成,这是来自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)
            + ')');
  }

答案 11 :(得分:3)

此代码应该运行得相当好:

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

答案 12 :(得分: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();
}

对于我的应用程序,我决定在退出时添加一个清除 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(这是完全可选的,你可以在此时通过递归来完成),但是我想要在安全的一面。

答案 13 :(得分: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提供了最接近被询问的&#34;标准&#34;并适用于JDK 7和旧版本。这也会返回一个&#34; old&#34;文件实例(基于流)而不是&#34; new&#34;路径实例(基于缓冲区,将是JDK7的getTemporaryDirectory()方法的结果) - &gt;因此,它返回大多数人在创建临时目录时所需的内容。

答案 14 :(得分:1)

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

答案 15 :(得分:1)

在Java 7之前,您还可以:

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

答案 16 :(得分: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。

注意:
阅读以下内容以递归方式删除目录:
Delete directories recursively in Java

答案 17 :(得分:0)

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