Java에서 임시 디렉토리 / 폴더를 만드는 방법은 무엇입니까?


Java 응용 프로그램 내에서 임시 디렉토리를 만드는 표준적이고 신뢰할 수있는 방법이 있습니까? 주석에 약간의 코드가있는 Java 이슈 데이터베이스에 항목이 있지만 일반적인 라이브러리 (Apache Commons 등) 중 하나에서 찾을 수있는 표준 솔루션이 있는지 궁금합니다.




Answers


JDK 7을 사용하는 경우 새로운 Files.createTempDirectory 클래스를 사용하여 임시 디렉토리를 작성하십시오.

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 라이브러리에는 유용한 유틸리티가 많이 있습니다. 여기서 주목해야 할 것은 Files 클래스 입니다. 그것은 다음과 같은 유용한 방법들을 가지고 있습니다 :

File myTempDir = Files.createTempDir();

이것은 당신이 한 줄에서 물어 본 것과 정확히 같습니다. 이 문서를 읽으면 File.createTempFile("install", "dir") 의 제안 된 수정이 일반적으로 보안 취약점을 File.createTempFile("install", "dir") 한다는 것을 알 수 있습니다.




테스트를 위해 임시 디렉토리가 필요하고 @Rule 을 사용하는 경우 TemporaryFolder 와 함께 @Rule 문제를 해결합니다.

@Rule
public TemporaryFolder folder = new TemporaryFolder();

문서에서 :

TemporaryFolder 규칙을 사용하면 테스트 방법이 완료 될 때 (통과 또는 실패 여부에 관계없이) 삭제 될 수있는 파일 및 폴더를 만들 수 있습니다.

참고 : http://junit.org/apidocs/org/junit/rules/TemporaryFolder.html




이 문제를 해결하기위한 순진한 코드는 여러 가지 답변을 포함하여 경쟁 조건으로 인해 어려움을 겪습니다. 역사적으로 경쟁 조건을주의 깊게 생각하고 직접 작성하거나 Spina의 대답처럼 Google의 Guava와 같은 제 3 자 라이브러리를 사용할 수도 있습니다. 또는 버그가있는 코드를 작성할 수도 있습니다.

그러나 JDK 7부터 좋은 소식이 있습니다! Java 표준 라이브러리 자체가 이제는이 문제에 대한 제대로 작동하는 (비 반응적인) 솔루션을 제공합니다. java.nio.file.Files # createTempDirectory ()를 원한다. 문서에서 :

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

이름을 생성하기 위해서 지정된 접두사를 사용해, 지정된 디렉토리에 새로운 디렉토리를 작성합니다. 결과 Path는 주어진 디렉토리와 동일한 FileSystem과 연관됩니다.

디렉토리의 이름이 어떻게 구성되는지에 대한 세부 사항은 구현에 따라 다르므로 지정되지 않습니다. 가능한 경우 접두사는 후보 이름을 구성하는 데 사용됩니다.

이것은 Sun 버그 추적기에있는 당황스러운 고대 버그 보고서 를 효과적으로 해결하여 그러한 기능을 요구했습니다.




이것은 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;

여기를 보아라




나중에 명시 적으로 삭제하더라도 deleteOnExit() 사용하지 마십시오.

Google 'deleteonexit is evil'에 대한 자세한 정보는 문제의 요점은 다음과 같습니다.

  1. deleteOnExit() 은 일반적인 JVM 종료 만 삭제하고 JVM 프로세스가 손상되거나 종료되지 않습니다.

  2. deleteOnExit() 은 JVM 종료시 삭제 deleteOnExit() 기 때문에 다음과 같은 이유로 장시간 실행되는 서버 프로세스에는 적합하지 않습니다.

  3. 가장 나쁜 것 - deleteOnExit() 는 각 임시 파일 항목에 대해 메모리를 사용합니다. 프로세스가 몇 달 동안 실행 중이거나 짧은 시간 내에 많은 임시 파일을 생성하는 경우 메모리를 소비하고 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() 을 호출하는 것을 기억해야합니다. 그렇지 않으면 완료 후에 파일이 삭제되지 않습니다.




나는 똑같은 문제가있어서 관심있는 사람들을위한 또 하나의 대답 일 뿐이다. 위의 질문들 중 하나와 비슷하다.

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

이 메서드는 callstack을 사용하지 않고 temp 를 삭제하기 전에 모든 하위 디렉토리와 파일을 삭제합니다 (이 옵션은 완전히 선택 사항이며이 시점에서 재귀를 사용하여 수행 할 수 있음).하지만 나는 안전한쪽으로 가고 싶습니다.




완료만으로도 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)
            + ')');
  }



이 코드는 정상적으로 작동해야합니다.

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()) 메소드 호출에 대한 테스트가 끝나면 다른 프로세스가 if(newTempDir.mkdirs()) 있습니다. File.createTempFile() 안에 묻혀있는 원시 코드를 사용하지 않고이 코드를 완전히 안전하게 만드는 방법을 모르겠습니다.




다른 대답에서 볼 수 있듯이 표준 접근법이 발생하지 않았습니다. 따라서 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;

}

이것은 아파치가 요청 된 "표준"에 가장 가까운 라이브러리를 공유하고 JDK 7과 이전 버전 모두에서 작동하기 때문에 선호됩니다. 이것은 또한 "새로운"Path 인스턴스 (버퍼 기반이며 JDK7의 getTemporaryDirectory () 메소드의 결과 일 것입니다)가 아닌 "오래된"File 인스턴스를 반환합니다.> 따라서 대부분의 사람들이 필요로하는 것을 리턴합니다. 그들은 임시 디렉토리를 만들고자합니다.




Java 7 이전에는 다음 작업도 수행 할 수있었습니다.

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



File#createTempFile 을 사용하고 디렉토리의 고유 한 이름을 만들려면 delete 하십시오. JVM 종료시 (재귀 적으로) 디렉토리를 삭제하려면 ShutdownHook 을 추가해야합니다.