guardar - ficheros en java
¿Cómo crear un directorio/carpeta temporal en Java? (17)
¿Existe una forma estándar y confiable de crear un directorio temporal dentro de una aplicación Java? Hay una entrada en la base de datos de problemas de Java , que tiene un poco de código en los comentarios, pero me pregunto si hay una solución estándar que se encuentre en una de las bibliotecas habituales (Apache Commons, etc.).
Bueno, "createTempFile" realmente crea el archivo. Entonces, ¿por qué no simplemente eliminarlo primero, y luego hacer el mkdir en él?
Este código debería funcionar razonablemente bien:
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;
}
Como se discutió en este RFE y sus comentarios, puede llamar a tempDir.delete()
primero. O puede usar System.getProperty("java.io.tmpdir")
y crear allí un directorio. De cualquier manera, recuerde llamar a tempDir.deleteOnExit()
, o el archivo no se eliminará cuando haya terminado.
Usar File#createTempFile
y delete
para crear un nombre único para el directorio parece estar bien. Debería agregar un ShutdownHook
para eliminar el directorio (recursivamente) en el cierre de JVM.
Esto es lo que decidí hacer por mi propio código:
/**
* 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();
}
No use deleteOnExit()
incluso si lo elimina explícitamente más tarde.
Google ''deleteonexit is evil'' para más información, pero la esencia del problema es:
deleteOnExit()
solo se elimina para las paradas normales de JVM, no bloquea o elimina el proceso de JVM.deleteOnExit()
solo se elimina en el cierre de JVM, lo que no es bueno para los procesos de servidor de ejecución prolongada porque:El más malvado de todos:
deleteOnExit()
consume memoria para cada entrada de archivo temporal. Si su proceso se ejecuta durante meses, o crea muchos archivos temporales en poco tiempo, consume memoria y nunca lo suelta hasta que la JVM se apaga.
Me gustan los múltiples intentos de crear un nombre único, pero incluso esta solución no excluye una condición de carrera. Otro proceso puede deslizarse después de la prueba de la invocación del método exists()
y if(newTempDir.mkdirs())
. No tengo idea de cómo hacerlo completamente seguro sin recurrir al código nativo, que supongo que es lo que está enterrado dentro de File.createTempFile()
.
El código ingenuamente escrito para resolver este problema sufre de condiciones de carrera, incluidas varias de las respuestas aquí. Históricamente, podrías pensar detenidamente sobre las condiciones de carrera y escribirlo tú mismo, o podrías usar una biblioteca de terceros como la Guava de Google (como sugirió la respuesta de Spina). O podrías escribir un código defectuoso.
Pero a partir de JDK 7, ¡hay buenas noticias! La biblioteca estándar de Java ahora proporciona una solución que funciona correctamente (no agresiva) para este problema. Desea java.nio.file.Files # createTempDirectory () . De la documentación :
public static Path createTempDirectory(Path dir,
String prefix,
FileAttribute<?>... attrs)
throws IOException
Crea un nuevo directorio en el directorio especificado, usando el prefijo dado para generar su nombre. La ruta de acceso resultante está asociada con el mismo sistema de archivos que el directorio dado.
Los detalles sobre cómo se construye el nombre del directorio dependen de la implementación y, por lo tanto, no están especificados. Donde sea posible, el prefijo se usa para construir nombres de candidatos.
Esto efectivamente resuelve el informe de error vergonzosamente antiguo en el rastreador de errores de Sun que pedía precisamente esa función.
A partir de Java 1.7 createTempDirectory(prefix, attrs)
y createTempDirectory(dir, prefix, attrs)
se incluyen en java.nio.file.Files
Ejemplo: File tempDir = Files.createTempDirectory("foobar").toFile();
Este es el código fuente de Files.createTempDir () de la biblioteca de Guava. No es tan complejo como se podría pensar:
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) + '')'');
}
Por defecto:
private static final int TEMP_DIR_ATTEMPTS = 10000;
Tengo el mismo problema, así que esta es solo otra respuesta para aquellos que están interesados, y es similar a uno de los anteriores:
public static final String tempDir = System.getProperty("java.io.tmpdir")+"tmp"+System.nanoTime();
static {
File f = new File(tempDir);
if(!f.exists())
f.mkdir();
}
Y para mi aplicación, decidí agregar una opción para borrar la temperatura en la salida, así que agregué un enlace de apagado:
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());
}
}
}
}
});
El método elimina todos los subdires y archivos antes de eliminar la temperatura , sin usar la pila de llamadas (que es totalmente opcional y puedes hacerlo con recurrencia en este punto), pero quiero estar seguro.
Si está utilizando JDK 7, use la nueva clase Files.createTempDirectory para crear el directorio temporal.
Antes de JDK 7 esto debería hacerlo:
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);
}
Podría hacer mejores excepciones (subclase IOException) si lo desea.
Como puede ver en las otras respuestas, no ha surgido un enfoque estándar. Por lo tanto, ya mencionó Apache Commons, propongo el siguiente enfoque utilizando FileUtils de 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;
}
Esto es preferible ya que apache comparte la biblioteca que se acerca más al "estándar" solicitado y funciona tanto con JDK 7 como con versiones anteriores. Esto también devuelve una instancia de archivo "antigua" (que se basa en secuencias) y no una instancia de ruta "nueva" (que se basa en un búfer y sería el resultado del método getTemporaryDirectory () de JDK7). Por lo tanto, devuelve lo que la mayoría necesita Quieren crear un directorio temporal.
Si necesita un directorio temporal para probar y está usando jUnit, @Rule
junto con TemporaryFolder
resuelve su problema:
@Rule
public TemporaryFolder folder = new TemporaryFolder();
De la documentación:
La regla TemporaryFolder permite la creación de archivos y carpetas que se garantiza que se eliminarán cuando finalice el método de prueba (ya sea que pase o falle)
Ref: http://junit.org/apidocs/org/junit/rules/TemporaryFolder.html
La biblioteca de Google Guava tiene toneladas de utilidades útiles. Una de las notas aquí es la clase de archivos . Tiene un montón de métodos útiles que incluyen:
File myTempDir = Files.createTempDir();
Esto hace exactamente lo que pediste en una línea. Si lee la documentación aquí , verá que la adaptación propuesta de File.createTempFile("install", "dir")
generalmente presenta vulnerabilidades de seguridad.
Solo para completar, este es el código de la biblioteca de google guava. No es mi código, pero creo que es valioso mostrarlo aquí en este hilo.
/** 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)
+ '')'');
}
Antes de Java 7 también podías:
File folder = File.createTempFile("testFileUtils", ""); // no suffix
folder.delete();
folder.mkdirs();
folder.deleteOnExit();