Javaアプリケーション内に一時ディレクトリを作成する標準的で信頼できる方法はありますか? というJavaのissueデータベース にエントリがあります。コメントには少しコードが含まれていますが、通常のライブラリのいずれかに標準的な解決策があるのではないでしょうか。 (Apache Commonsなど)
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を使用している場合は、TemporaryFolder
と一緒に@Rule
を使用すると問題が解決します。
@Rule
public TemporaryFolder folder = new TemporaryFolder();
TemporaryFolderルールでは、テストメソッドが終了したときに削除されることが保証されているファイルとフォルダを作成できます(成功または失敗)。
更新:
JUnit Jupiter(バージョン5.1.1以降)を使用している場合は、JUnit 5 Extension PackであるJUnit Pioneerを使用することができます。
プロジェクトのドキュメント からコピーしたもの:
たとえば、次のテストは単一のテストメソッドの拡張子を登録し、ファイルを作成して一時ディレクトリに書き込み、その内容を確認します。
@Test
@ExtendWith(TempDirectory.class)
void test(@TempDir Path tempDir) {
Path file = tempDir.resolve("test.txt");
writeFile(file);
assertExpectedFileContent(file);
}
TempDirectoryの JavaDoc および JavaDoc の詳細情報
Gradle:
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));
}
この問題を解決するために単純に書かれたコードは、ここにあるいくつかの答えを含む競合状態に苦しんでいます。歴史的には、競合条件について慎重に考えてそれを自分で書くことも、(Spinaの回答が示唆したように)GoogleのGuavaのようなサードパーティのライブラリを使うこともできます。あるいはバグのあるコードを書くこともできます。
しかし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()
を使用しないでください。
グーグルの 'deleteonexit is evil' で詳細を見ることができますが、問題の要旨は次のとおりです。
deleteOnExit()
は通常のJVMシャットダウンのためにのみ削除され、JVMプロセスのクラッシュや強制終了はしません。
deleteOnExit()
はJVMのシャットダウン時にのみ削除されます - 長時間実行されているサーバープロセスには不適切です。
最も邪悪な - 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を実行しないのはなぜですか?
完成のためだけに、これはgoogle guava libraryのコードです。これは私のコードではありませんが、このスレッドでここに表示することは価値があると思います。
/** 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)
+ ')');
}
このRFE とそのコメントで説明したように、最初にtempDir.delete()
を呼び出すことができます。あるいはSystem.getProperty("Java.io.tmpdir")
を使ってそこにディレクトリを作成することもできます。いずれにせよ、あなたはtempDir.deleteOnExit()
を呼び出すことを忘れないでください、さもなければファイルはあなたがした後に削除されません。
このコードはかなりうまくいくはずです。
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;
}
私は同じ問題を抱えていたので、これは興味を持っている人たちのための単なるもう一つの答えであり、それは上記のうちの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());
}
}
}
}
});
このメソッドは、コールスタックを使用せずに、一時を削除する前にすべてのサブディレクトリとファイルを削除します。しかし、私は安全な側になりたいのです。
他の答えでわかるように、標準的なアプローチは発生していません。したがって、すでに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とそれ以前のバージョンの両方で動作するライブラリを共有しているので、これは好ましいです。これはまた "古い" Fileインスタンス(これはストリームベースである)を返し、 "new" Pathインスタンス(これはバッファベースでありJDK 7のgetTemporaryDirectory()メソッドの結果であろう)ではなく - >それゆえほとんどの人が必要とするものを返す。彼らは一時ディレクトリを作りたいのです。
Java 7より前は、次のことも可能でした。
File folder = File.createTempFile("testFileUtils", ""); // no suffix
folder.delete();
folder.mkdirs();
folder.deleteOnExit();
私はユニークな名前を作成するという複数の試みが好きですが、この解決策でさえ競合状態を除外しません。 exists()
のテストとif(newTempDir.mkdirs())
メソッド呼び出しの後に別のプロセスが入り込む可能性があります。ネイティブコードに頼らずにこれを完全に安全にする方法はわかりません。これはFile.createTempFile()
に埋め込まれていると思います。
ディレクトリの一意の名前を作成するためにFile#createTempFile
とdelete
を使用しても問題ありません。 JVMのシャットダウン時にディレクトリを(再帰的に)削除するには、ShutdownHook
を追加してください。