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ライブラリには、数多くの有用なユーティリティがあります。 ここで注意すべき点の1つはFilesクラスです。 それは有用な方法の束を含んでいます:

File myTempDir = Files.createTempDir();

これは、あなたが1行で求めたこととまったく同じです。 ここでドキュメントを読むと、 File.createTempFile("install", "dir")の提案された適応がFile.createTempFile("install", "dir")通常、セキュリティ上の脆弱性をFile.createTempFile("install", "dir")File.createTempFile("install", "dir")ます。




テストのために一時ディレクトリが必要で、 @Ruleを使用している場合、 @Ruleとともに@Ruleが問題を解決します:

@Rule
public TemporaryFolder folder = new TemporaryFolder();

ドキュメントから:

テンポラリフォルダルールを使用すると、テストメソッドが終了したときに削除されることが保証されているファイルとフォルダを作成できます(パスするか失敗するかに関係なく)

Ref: http : //junit.org/apidocs/org/junit/rules/TemporaryFolder.html




この問題を解決するための純粋に書かれたコードは、ここでのいくつかの回答を含む競争条件に苦しんでいます。 歴史的には、競合状態を注意深く考え、自分で書くことができます。また、GoogleのGuava(Spinaの回答が示唆しているような)などのサードパーティのライブラリを使用することもできます。

しかし、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プロセスのdeleteOnExit()ません。

  2. deleteOnExit()はJVMシャットダウン時にのみ削除されます。

  3. 最も悪いall - 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()を呼び出すことを忘れないでください。そうしないと、ファイルは削除されません。




私は同じ問題を抱えているので、これは関心のある人のためのもう1つの答えであり、それは上記の1つに似ています:

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を削除する前にすべてのサブディレクトリとファイルを削除しますが、私は安全面にいたいと思います。




完了のためだけに、これは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)
            + ')');
  }



このコードはうまくいくはずです:

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;

}

これは、apacheが要求された "標準"に最も近いライブラリをコモディンし、JDK 7とそれ以前のバージョンの両方で動作するので、これが好ましい。 これは、 "新しい" Pathインスタンス(バッファベースであり、JDK7のgetTemporaryDirectory()メソッドの結果となる)ではなく、 "古い" Fileインスタンスも返します。>したがって、ほとんどの人が必要とするものを返します一時的なディレクトリを作成する必要があります。




Java 7より前には、次のことも可能でした:

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



File#createTempFiledeleteし、ディレクトリの一意の名前を作成するためにdeleteするとOKです。 JVMシャットダウン時にディレクトリを(再帰的に)削除するには、 ShutdownHookを追加する必要があります。