Javaアプリケーション内に一時ディレクトリを作成する標準的で信頼できる方法はありますか?コメントに少しコードが含まれているJavaの課題データベースにエントリがありますが、通常のライブラリ(Apache Commonsなど)の1つに標準的な解決策があるかどうか疑問に思います。
Javaアプリケーション内に一時ディレクトリを作成する標準的で信頼できる方法はありますか?コメントに少しコードが含まれているJavaの課題データベースにエントリがありますが、通常のライブラリ(Apache Commonsなど)の1つに標準的な解決策があるかどうか疑問に思います。
回答:
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)を作成できます。
temp.delete(); temp = new File(temp.getPath + ".d"); temp.mkdir(); ..., temp.delete();
です。
delete()
との間で競合状態にありmkdir()
ます。悪意のあるプロセスがその間にターゲットディレクトリを作成する可能性があります(最近作成されたファイルの名前を使用)。Files.createTempDir()
代替案については、を参照してください。
Google Guavaライブラリには、便利なユーティリティがたくさんあります。ここで注目すべきは、Filesクラスです。次のような便利なメソッドがたくさんあります。
File myTempDir = Files.createTempDir();
これは、あなたが一行で要求したことを正確に行います。ここのドキュメントを読むと、の提案された適応がFile.createTempFile("install", "dir")
通常、セキュリティの脆弱性をもたらすことがわかります。
テストに一時ディレクトリが必要で、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);
}
詳細は 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));
}
この問題を解決するために単純に書かれたコードは、ここでの回答のいくつかを含む競合状態に悩まされています。歴史的には、競合状態を注意深く考えて自分で作成するか、GoogleのGuava(Spinaの回答が示唆するように)などのサードパーティライブラリを使用するか、バグのあるコードを作成することができます。
しかし、JDK 7以降、朗報があります。Java標準ライブラリ自体が、この問題に対して適切に機能する(レイシーではない)ソリューションを提供するようになりました。あなたは欲しい)(java.nio.file.Files#のcreateTempDirectory。ドキュメントから:
public static Path createTempDirectory(Path dir,
String prefix,
FileAttribute<?>... attrs)
throws IOException
指定されたプレフィックスを使用して、指定されたディレクトリに新しいディレクトリを作成し、その名前を生成します。結果のパスは、指定されたディレクトリと同じファイルシステムに関連付けられています。
ディレクトリ名の構成方法の詳細は実装に依存するため、指定されていません。可能な場合、プレフィックスは候補名の作成に使用されます。
これにより、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がシャットダウンするまで解放しないでください。
これは私が自分のコードのためにやることに決めたものです:
/**
* 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を実行しないのですか?
このコードはかなりうまくいくはずです:
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()
必要があります。そうしないと、ファイルは完了後に削除されません。
完成のために、これは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 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を削除する前にすべてのサブディレクトリとファイルを削除します(これは完全にオプションであり、この時点で再帰を使用して実行できます)が、安全な側になりたいと思います。
他の回答でわかるように、標準的なアプローチは発生していません。したがって、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()メソッドの結果である「新しい」パスインスタンスではありません。彼らは一時ディレクトリを作成したいと考えています。
Java 7より前は、次のこともできました。
File folder = File.createTempFile("testFileUtils", ""); // no suffix
folder.delete();
folder.mkdirs();
folder.deleteOnExit();
この小さな例を試してください:
コード:
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でディレクトリを再帰的に削除します
とを使用してディレクトリに一意の名前を作成しても問題File#createTempFile
ありdelete
ません。ShutdownHook
JVMのシャットダウン時にディレクトリを(再帰的に)削除するには、を追加する必要があります。