Java 애플리케이션 내에 임시 디렉토리를 작성하는 표준적이고 안정적인 방법이 있습니까? 거기에 자바의 문제 데이터베이스의 항목 코멘트 코드의 비트를 가지고,하지만 난 보통 라이브러리 (아파치 코 몬즈 등) 중 하나에서 찾을 수있는 표준 용액이 있는지 궁금해?
Java 애플리케이션 내에 임시 디렉토리를 작성하는 표준적이고 안정적인 방법이 있습니까? 거기에 자바의 문제 데이터베이스의 항목 코멘트 코드의 비트를 가지고,하지만 난 보통 라이브러리 (아파치 코 몬즈 등) 중 하나에서 찾을 수있는 표준 용액이 있는지 궁금해?
답변:
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 라이브러리에는 유용한 유틸리티가 많이 있습니다. 여기서 주목할 것은 파일 클래스 입니다. 다음과 같은 유용한 방법이 있습니다.
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 Extension Pack 인 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을 목성 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
지정된 접두사를 사용하여 지정된 디렉토리에 새 디렉토리를 작성하여 이름을 생성합니다. 결과 경로는 주어진 디렉토리와 동일한 파일 시스템과 연관됩니다.
디렉토리 이름의 구성 방법에 대한 세부 사항은 구현에 따라 다르므로 지정되지 않았습니다. 가능한 경우 접두사가 후보 이름을 구성하는 데 사용됩니다.
이를 통해 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()
나중에 명시 적으로 삭제하더라도 사용하지 마십시오 .
더 많은 정보를 얻으 려면 Google '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()
그렇지 않으면 파일이 완료된 후 삭제되지 않습니다.
완료를 위해 이것은 구글 구아바 라이브러리의 코드입니다. 내 코드는 아니지만이 스레드에서 여기에 표시하는 것이 가치 있다고 생각합니다.
/** 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());
}
}
}
}
});
이 방법 은 callstack을 사용하지 않고 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;
}
아파치는 요청 된 "표준"에 가장 근접한 라이브러리를 공통으로 사용하고 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 종료시 디렉토리를 (재귀 적으로) 삭제하려면 a 를 추가해야합니다 .