มีวิธีมาตรฐานและเชื่อถือได้ในการสร้างไดเร็กทอรีชั่วคราวภายในแอ็พพลิเคชัน Java หรือไม่? มีรายการในฐานข้อมูลปัญหาของ Javaซึ่งมีรหัสเล็กน้อยในความคิดเห็น แต่ฉันสงสัยว่ามีวิธีการแก้ปัญหามาตรฐานที่จะพบในหนึ่งในห้องสมุดปกติ (Apache Commons เป็นต้น)?
มีวิธีมาตรฐานและเชื่อถือได้ในการสร้างไดเร็กทอรีชั่วคราวภายในแอ็พพลิเคชัน Java หรือไม่? มีรายการในฐานข้อมูลปัญหาของ Javaซึ่งมีรหัสเล็กน้อยในความคิดเห็น แต่ฉันสงสัยว่ามีวิธีการแก้ปัญหามาตรฐานที่จะพบในหนึ่งในห้องสมุดปกติ (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);
}
คุณสามารถทำการยกเว้นที่ดีกว่า (subclass IOException) ได้ถ้าต้องการ
temp.delete(); temp = new File(temp.getPath + ".d"); temp.mkdir(); ..., temp.delete();
ดังนั้นวิธีที่ปลอดภัยมากขึ้น
delete()
และmkdir()
: กระบวนการที่เป็นอันตรายสามารถสร้างไดเรกทอรีเป้าหมายได้ในระหว่างนี้ (ใช้ชื่อไฟล์ที่เพิ่งสร้างขึ้น) ดูFiles.createTempDir()
ทางเลือกอื่น
ห้องสมุด Google Guava มีสาธารณูปโภคที่เป็นประโยชน์มากมาย หนึ่งของโน้ตที่นี่เป็นระดับไฟล์ มันมีวิธีการที่เป็นประโยชน์มากมายเช่น:
File myTempDir = Files.createTempDir();
นี่เป็นสิ่งที่คุณต้องการในหนึ่งบรรทัด หากคุณอ่านเอกสารที่นี่คุณจะเห็นว่าการดัดแปลงที่เสนอFile.createTempFile("install", "dir")
โดยทั่วไปจะแนะนำช่องโหว่ด้านความปลอดภัย
หากคุณต้องการไดเรกทอรีชั่วคราวสำหรับการทดสอบและคุณใช้ jUnit @Rule
ร่วมกับTemporaryFolder
แก้ไขปัญหาของคุณ:
@Rule
public TemporaryFolder folder = new TemporaryFolder();
จากเอกสาร :
กฎโฟลเดอร์ชั่วคราวช่วยให้การสร้างไฟล์และโฟลเดอร์ที่รับประกันว่าจะถูกลบเมื่อวิธีการทดสอบเสร็จสิ้น (ไม่ว่าจะผ่านหรือล้มเหลว)
ปรับปรุง:
หากคุณกำลังใช้ JUnit Jupiter (เวอร์ชัน 5.1.1 หรือสูงกว่า) คุณมีตัวเลือกในการใช้ JUnit Pioneer ซึ่งเป็น JUnit 5 Extension Pack
คัดลอกมาจากเอกสารโครงการ :
ตัวอย่างเช่นการทดสอบต่อไปนี้ลงทะเบียนส่วนขยายสำหรับวิธีการทดสอบเดียวสร้างและเขียนไฟล์ไปยังไดเรกทอรีชั่วคราวและตรวจสอบเนื้อหา
@Test
@ExtendWith(TempDirectory.class)
void test(@TempDir Path tempDir) {
Path file = tempDir.resolve("test.txt");
writeFile(file);
assertExpectedFileContent(file);
}
ข้อมูลเพิ่มเติมในJavaDocและJavaDoc ของ TempDirectory
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));
}
โค้ดที่เขียนอย่างไร้เดียงสาเพื่อแก้ปัญหานี้ได้รับผลกระทบจากสภาพการแข่งขัน ในอดีตคุณสามารถคิดอย่างรอบคอบเกี่ยวกับสภาพการแข่งขันและเขียนด้วยตัวคุณเองหรือคุณอาจใช้ห้องสมุดบุคคลที่สามเช่น Guava ของ Google (ตามคำแนะนำของ Spina) หรือคุณสามารถเขียนรหัสบั๊กกี้ได้
แต่ตั้งแต่ JDK 7 มีข่าวดี! ขณะนี้ไลบรารีมาตรฐานของ Java เองนั้นมีวิธีแก้ปัญหาที่ทำงานได้อย่างเหมาะสม คุณต้องการjava.nio.file.Files # createTempDirectory () จากเอกสาร :
public static Path createTempDirectory(Path dir,
String prefix,
FileAttribute<?>... attrs)
throws IOException
สร้างไดเรกทอรีใหม่ในไดเรกทอรีที่ระบุโดยใช้คำนำหน้าที่กำหนดเพื่อสร้างชื่อ เส้นทางที่ได้จะเชื่อมโยงกับ FileSystem เดียวกันกับไดเรกทอรีที่กำหนด
รายละเอียดเกี่ยวกับวิธีการสร้างชื่อของไดเรกทอรีนั้นขึ้นอยู่กับการใช้งานและไม่ได้ระบุ จะใช้คำนำหน้าเพื่อสร้างชื่อผู้สมัคร
วิธีนี้จะช่วยแก้ไขรายงานบักแบบโบราณที่น่าอับอายในตัวติดตามบั๊ก Sun ซึ่งขอฟังก์ชันดังกล่าว
นี่คือซอร์สโค้ดของ Files.createTempDir () ของไลบรารี Guava มันไม่มีอะไรซับซ้อนเท่าที่คุณคิด:
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 นั้นชั่วร้าย'สำหรับข้อมูลเพิ่มเติม แต่ส่วนสำคัญของปัญหาคือ:
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 กับมัน
รหัสนี้ควรใช้งานได้ดีพอสมควร:
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();
}
และสำหรับแอปพลิเคชันของฉันฉันตัดสินใจที่จะเพิ่มตัวเลือกในการล้างtemp on exit ดังนั้นฉันจึงเพิ่มลงใน hook shut-down:
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โดยไม่ต้องใช้ callstack (ซึ่งเป็นทางเลือกโดยสิ้นเชิงและคุณสามารถทำได้ด้วยการเรียกซ้ำในตอนนี้) แต่ฉันต้องการอยู่ในด้านที่ปลอดภัย
อย่างที่คุณเห็นในคำตอบอื่น ๆ ไม่มีวิธีการมาตรฐานเกิดขึ้น ดังนั้นคุณพูดถึง Apache Commons แล้วฉันเสนอวิธีการต่อไปนี้โดยใช้ FileUtils จากApache Commons IO :
/**
* 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 และรุ่นที่เก่ากว่า สิ่งนี้จะส่งคืนอินสแตนซ์ไฟล์ "เก่า" (ซึ่งเป็นแบบสตรีม) และไม่ใช่อินสแตนซ์พา ธ "ใหม่" (ซึ่งเป็นบัฟเฟอร์ตามและจะเป็นผลลัพธ์ของเมธอด getTemporaryDirectory () ของ JDK7 -> ดังนั้นจึงส่งคืนสิ่งที่คนส่วนใหญ่ต้องการเมื่อ พวกเขาต้องการสร้างไดเรกทอรีชั่วคราว
ฉันชอบความพยายามหลายครั้งในการสร้างชื่อที่ไม่ซ้ำกัน แต่วิธีนี้ไม่ได้ออกกฎการแย่งชิง กระบวนการอื่นสามารถส่งหลังจากการทดสอบexists()
และif(newTempDir.mkdirs())
การเรียกใช้เมธอด ฉันมีความคิดวิธีการที่จะทำให้สมบูรณ์ปลอดภัยนี้โดยไม่ต้อง resorting File.createTempFile()
รหัสพื้นเมืองซึ่งผมคิดว่าเป็นสิ่งที่ฝังอยู่ภายใน
ก่อนหน้า 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 \ ชื่อผู้ใช้ \ AppData \ Local \ Temp \ tmpDir2908538301081367877
ความคิดเห็น:
Files.createTempDirectory สร้าง ID เฉพาะ atomatically - 2908538301081367877
หมายเหตุ:
อ่านต่อไปนี้สำหรับการลบไดเรกทอรีซ้ำ:
ลบไดเรกทอรีซ้ำใน Java
การใช้File#createTempFile
และdelete
เพื่อสร้างชื่อที่ไม่ซ้ำสำหรับไดเรกทอรีดูเหมือนว่าใช้ได้ คุณควรเพิ่ม a ShutdownHook
เพื่อลบไดเร็กทอรี (เรียกซ้ำ) ในการปิด JVM