Javaで一時ディレクトリ/フォルダを作成するにはどうすればよいですか?


364

Javaアプリケーション内に一時ディレクトリを作成する標準的で信頼できる方法はありますか?コメントに少しコードが含まれているJavaの課題データベースにエントリがありますが、通常のライブラリ(Apache Commonsなど)の1つに標準的な解決策があるかどうか疑問に思います。

回答:


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

4
@Demiurg Windowsでファイルが既に開かれている場合(ファイルがウイルススキャナーなどで開かれている可能性があります)、ファイルがすぐに削除されない場合があります。他に表示する他のドキュメントはありますか(私はそのようなことに興味があります:-)?定期的に発生する場合、上記のコードは機能しません。まれな場合は、削除が発生する(または、最大試行回数に達する)まで、上記のコードへの呼び出しを繰り返します。
TofuBeer

6
@Demiurg Javaはファイルをすぐに削除しないことが知られています。開かなくてもそうです。したがって、より安全な方法はtemp.delete(); temp = new File(temp.getPath + ".d"); temp.mkdir(); ..., temp.delete();です。
謝Jìléi

102
このコードは、delete()との間で競合状態にありmkdir()ます。悪意のあるプロセスがその間にターゲットディレクトリを作成する可能性があります(最近作成されたファイルの名前を使用)。Files.createTempDir()代替案については、を参照してください。
Joachim Sauer

11
私は好き !目立ちます。見逃しがちです。私は学生によって書かれた多くのコードを読みました... if(!i)は迷惑に
なるほど

182

Google Guavaライブラリには、便利なユーティリティがたくさんあります。ここで注目すべきは、Filesクラスです。次のような便利なメソッドがたくさんあります。

File myTempDir = Files.createTempDir();

これは、あなたが一行で要求したことを正確に行います。ここのドキュメントを読むと、の提案された適応がFile.createTempFile("install", "dir")通常、セキュリティの脆弱性をもたらすことがわかります。


あなたはどの脆弱性について言及しているのでしょうか。File.mkdir()がすでに存在する場合(攻撃者によって作成された場合)、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ここに空欄を埋めてくれてありがとう。私はかなり長い間、これについてぼんやりと考えていました。
Spina 2013年

168

テストに一時ディレクトリが必要で、jUnitを使用している場合は、で問題@RuleTemporaryFolder解決します。

@Rule
public TemporaryFolder folder = new TemporaryFolder();

ドキュメントから:

TemporaryFolderルールを使用すると、テストメソッドが終了したときに(成功したか失敗したかにかかわらず)削除されることが保証されているファイルとフォルダーを作成できます。


更新:

JUnit Jupiter(バージョン5.1.1以降)を使用している場合は、JUnit 5拡張パックであるJUnit Pioneerを使用するオプションがあります。

プロジェクトのドキュメントからコピー:

たとえば、次のテストでは、単一のテストメソッドの拡張を登録し、ファイルを作成して一時ディレクトリに書き込み、その内容を確認します。

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

詳細は TempDocのJavaDocおよび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の木星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

Windows 7のJUnit 4.8.2では動作しません!(権限の問題)
例外

2
@CraigRinger:なぜこれに頼るのが賢明ではないのですか?
アダムパーキン2013年

2
@AdamParkin正直なところ、もう覚えていません。説明が失敗しました!
クレイグリンガー2013年

1
このアプローチの主な利点は、ディレクトリがJUnitによって管理されることです(テスト前に作成され、テスト後に再帰的に削除されます)。そしてそれは機能します。「temp dir not created」というメッセージが表示された場合は、@ 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バグトラッカーのこのような機能だけを要求する、非常に古くからあるバグレポートが効果的に解決れます。


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 is evil」ですが、問題の要点は次のとおりです。

  1. deleteOnExit() 通常のJVMシャットダウンの場合にのみ削除され、JVMプロセスのクラッシュや強制終了は行われません。

  2. deleteOnExit() JVMのシャットダウン時にのみ削除-長期実行サーバープロセスには適していません。

  3. すべての中で最も悪- deleteOnExit()一時ファイルのエントリごとにメモリを消費します。プロセスが数か月間実行されている場合、または短時間で多数の一時ファイルが作成される場合は、メモリを消費し、JVMがシャットダウンするまで解放しないでください。


1
クラスとjarファイルがJVMによって作成された下に隠しファイルを取得するJVMがあり、この追加情報を削除するにはかなりの時間がかかります。WARが爆発するWebコンテナでホット再デプロイを実行する場合、JVMは文字通り、終了後、数時間実行して終了する前にクリーンアップするのに数分かかることがあります。
–ThorbjørnRavn Andersen

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は暗号学的に強力な疑似乱数ジェネレータを使用して生成されます。」悪意のあるプロセスが、exists()とmkdirs()の間で同じ名前のディレクトリをどのように作成するかを考えると、実際、これを今見てみると、私のexists()テストは少しばかげているかもしれません。
キース

キース:この場合、UUIDが安全であるかどうかは重要ではありません。あなたが何とかして「漏らした」とあなたが尋ねた名前についての情報にはそれで十分です。たとえば、作成中のファイルがNFSファイルシステム上にあり、攻撃者がパケットを(パッシブに)リスニングできるとします。または、ランダムジェネレーターの状態がリークされました。私のコメントで、私はあなたの解決策は受け入れられた回答と同じくらい安全ではないと述べましたが、これは公平ではありません。それにもかかわらず、いくつかのシナリオではそれは確かに可能です。
zbyszek

2
私は同じ考えを持ち、このようなランダムなUUIDビットを使用してソリューションを実装しました。チェックは存在せず、1回の作成試行のみです。randomUUIDメソッドで使用される強力なRNGは、衝突がないことをほとんど保証します(DBテーブルで主キーを生成するために使用でき、これを自分で実行し、衝突を認識できません)。不明な点がある場合は、stackoverflow.com
questions / 2513573 /…

Javaの実装を見ると、衝突がなくなるまでランダムな名前が生成されるだけです。彼らの最大の試みは無限です。したがって、悪意のある誰かがあなたのファイル/ディレクトリ名を推測し続けると、それは永久にループします。ここでは、ソースへのリンクは次のとおりです。hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/9fb81d7a2f16/src/share/...私はそれが何らかの形でそれがアトミックに一意の名前を生成することができるようにファイルシステムをロックし、できることを考えていましたディレクトリを作成しますが、ソースコードによるとそれはしないと思います。
18

5

「createTempFile」は実際にファイルを作成します。では、なぜ最初にそれを削除してから、それにmkdirを実行しないのですか?


1
常にmkdir()の戻り値を確認する必要があります。それがfalseの場合は、ディレクトリがすでに存在していることを意味します。これはセキュリティ上の問題を引き起こす可能性があるため、アプリケーションでエラーが発生するかどうかを検討してください。
サレルボサ

1
他の回答の競合状態に関する注記を参照してください。
Volker Stolz

レースを除いて、これが好きです
マーティンウィックマン2014

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()必要があります。そうしないと、ファイルは完了後に削除されません。


このプロパティは「... temp」ではなく「java.io.tmpdir」と呼ばれていませんか?java.sun.com/j2se/1.4.2/docs/api/java/io/File.htmlを
アンドリュース

かなりそうです。私が読んだことを繰り返す前に、私は確認するべきでした。
マイケルマイヤーズ

java.io.tmpdirは共有されているため、他の人のつま先を踏まないように、通常のブードゥー教をすべて実行する必要があります。
–ThorbjørnRavn Andersen

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

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と以前のバージョンの両方で動作するライブラリを共有するため、これが推奨されます。また、これは「ストリーム」ベースの「古い」ファイルインスタンスを返し、「バッファ」ベースであり、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
素敵なコード。ただし、Javaはフォルダ全体を一度に削除できないため、不快に「deleteOnExit()」は機能しません。すべてのファイルを再帰的に削除する必要があります:/
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#createTempFileありdeleteません。ShutdownHookJVMのシャットダウン時にディレクトリを(再帰的に)削除するには、を追加する必要があります。


シャットダウンフックは面倒です。File#deleteOnExitも機能しませんか?
Daniel Hiller

2
#deleteOnExitが機能しませんでした。空でないディレクトリは削除されないと思います。
muriloq 2009

Java 8で実行するクイックテストを実装しましたが、一時フォルダーは削除されませんでした。pastebin.com
mjgG70KGを
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.