Java アプリケーション内に一時ディレクトリを作成する標準的で信頼できる方法はありますか? Javaの問題データベースにエントリがあり、コメントにコードが少しありますが、通常のライブラリ(Apache Commonsなど)の1つに標準的な解決策があるのでしょうか?
18 に答える
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) を作成できます。
テスト用の一時ディレクトリが必要で、jUnit を使用している場合は、次の方法で問題@Rule
をTemporaryFolder
解決できます。
@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);
}
詳細については、JavaDocおよびTempDirectoryの JavaDoc を参照してください。
グレード:
dependencies {
testImplementation 'org.junit-pioneer:junit-pioneer:0.1.2'
}
メイヴン:
<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 ユーザーガイドからコピーした例:
@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));
}
この問題を解決するために素朴に書かれたコードは、ここでの回答のいくつかを含め、競合状態に悩まされます。歴史的には、競合状態について慎重に考えて自分で書くか、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;
Java 1.7 の時点でcreateTempDirectory(prefix, attrs)
、createTempDirectory(dir, prefix, attrs)
に含まれていますjava.nio.file.Files
例:
File tempDir = Files.createTempDirectory("foobar").toFile();
さて、「createTempFile」は実際にファイルを作成します。それでは、最初に削除してから mkdir を実行してみませんか?
このコードはかなりうまく機能するはずです:
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;
}
この 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());
}
}
}
}
});
このメソッドは、 tempを削除する前にすべてのサブディレクトリとファイルを削除しますが、コールスタックを使用せずに (これは完全にオプションであり、この時点で再帰を使用して行うことができます)、安全を確保したいと考えています。
この小さな例を試してください:
コード:
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
。
一意の名前を作成するための複数の試行が気に入っていますが、この解決策でも競合状態を排除することはできません. exists()
テストとif(newTempDir.mkdirs())
メソッド呼び出しの後に、別のプロセスが入り込む可能性があります。ネイティブ コードに頼らずにこれを完全に安全にする方法がわかりませんFile.createTempFile()
。
Java 7 より前は、次のこともできました。
File folder = File.createTempFile("testFileUtils", ""); // no suffix
folder.delete();
folder.mkdirs();
folder.deleteOnExit();
File#createTempFile
とを使用delete
してディレクトリに一意の名前を作成しても問題ないようです。ShutdownHook
JVM のシャットダウン時に (再帰的に) ディレクトリを削除するには、 を追加する必要があります。