Как создать временную папку / папку в Java?
Существует ли стандартный и надежный способ создания временного каталога внутри приложения Java? В базе данных проблем Java есть запись, в которой есть код кода, но мне интересно, есть ли стандартное решение, которое можно найти в одной из обычных библиотек (Apache Commons и т. Д.)?
- Создание каталога в / sdcard не выполняется
- Как получить файлы в относительном пути в C #
- Получить имя файла из URL-адреса
- «Ограниченная» папка / файлы в OS X El Capitan
- копирование содержимого двоичного файла
- Атомность `write (2)` в локальную файловую систему
- Ежедневный журнал Laravel, созданный с неправильными разрешениями
- Смешивание файлов и циклов
Если вы используете JDK 7, используйте новый class Files.createTempDirectory для создания временного каталога.
Перед 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); }
Вы можете сделать лучшие исключения (подclass IOException), если хотите.
В библиотеке Google Guava имеется множество полезных утилит. Здесь следует отметить class «Файлы» . Он содержит множество полезных методов, в том числе:
File myTempDir = Files.createTempDir();
Это делает именно то, что вы просили в одной строке. Если вы прочитаете здесь документацию, вы увидите, что предлагаемая адаптация File.createTempFile("install", "dir")
обычно вводит уязвимости безопасности.
Если вам нужен временный каталог для тестирования, и вы используете jUnit, @Rule
вместе с TemporaryFolder
решает вашу проблему:
@Rule public TemporaryFolder folder = new TemporaryFolder();
Из документации :
Правило TemporaryFolder позволяет создавать файлы и папки, которые гарантированно будут удалены при завершении метода тестирования (независимо от того, проходит он или нет)
Обновить:
Если вы используете JUnit Jupiter (версия 5.1.1 или выше), у вас есть возможность использовать JUnit Pioneer, который является пакетом расширения JUnit 5.
Скопировано из проектной документации :
Например, следующий тест регистрирует расширение для одного метода тестирования, создает и записывает файл во временный каталог и проверяет его содержимое.
@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:
org.junit-pioneer junit-pioneer 0.1.2 test
Наивно написанный код для решения этой проблемы страдает от условий гонки, включая несколько ответов здесь. Исторически вы могли бы тщательно подумать о состоянии гонки и написать ее самостоятельно, или вы могли бы использовать стороннюю библиотеку, такую как 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, который попросил именно такую функцию.
Это исходный код для файлов библиотеки 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, а неdeleteOnExit()
или уничтожению процесса 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 на нем?
Как обсуждалось в этом RFE и его комментариях, вы можете tempDir.delete()
вызвать tempDir.delete()
. Или вы можете использовать System.getProperty("java.io.tmpdir")
и создать там каталог. В любом случае, вы должны помнить, что вы вызываете tempDir.deleteOnExit()
, иначе файл не будет удален после завершения.
Для завершения это код из библиотеки google goava. Это не мой код, но я считаю его полезным показать здесь в этой теме.
/** 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. * * 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. * *
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 dirStack = new Stack (); 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()); } } } } });
Метод удаляет все поддиры и файлы перед удалением темпа, не используя столбец (который является полностью необязательным, и вы можете сделать это с рекурсией на данном этапе), но я хочу быть в безопасности.
Этот код должен работать достаточно хорошо:
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; }
Мне нравится несколько попыток создания уникального имени, но даже это решение не исключает условия гонки. Другой процесс может проскальзывать после теста для exists()
и вызова метода if(newTempDir.mkdirs())
. Я не знаю, как полностью сделать это безопасным, не прибегая к собственному коду, который, как я полагаю, является тем, что File.createTempFile()
внутри File.createTempFile()
.
Как вы можете видеть в других ответах, никакого стандартного подхода не возникло. Следовательно, вы уже упоминали 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, так и более старыми версиями. Это также возвращает «старый» экземпляр файла (который основан на streamе), а не «новый» экземпляр Path (который основан на буфере и будет результатом метода JDK7 getTemporaryDirectory ()) -> Поэтому он возвращает то, что нужно большинству людей, когда они хотят создать временную директорию.
Перед Java 7 вы также можете:
File folder = File.createTempFile("testFileUtils", ""); // no suffix folder.delete(); folder.mkdirs(); folder.deleteOnExit();
Использование File#createTempFile
и delete
для создания уникального имени для каталога выглядит нормально. Вы должны добавить ShutdownHook
для удаления каталога (рекурсивно) при отключении JVM.