files - java nio example
¿Alternativa a File.deleteOnExit() en Java NIO? (3)
Java IO tiene File.deleteOnExit() , que es un método que elimina el archivo al que se llama durante la terminación normal de la JVM. He encontrado que esto es muy útil para limpiar archivos temporales, especialmente durante las pruebas unitarias.
Sin embargo, no veo un método con el mismo nombre en la clase de Files Java NIO. Soy consciente de que puedo hacer path.toFile().deleteOnExit()
, pero me gustaría saber si hay una alternativa utilizando NIO.
¿Hay alguna alternativa? Si no, ¿por qué no hay uno?
Respuesta corta
No puede eliminar archivos arbitrarios en Java NIO, pero puede usar StandardOpenOption.DELETE_ON_CLOSE
cuando abra un nuevo flujo, que eliminará el archivo tan pronto como se cierre el flujo, ya sea llamando a .close()
(incluso desde un intento). declaración de recursos) o la terminación JVM. Por ejemplo:
Files.newOutputStream(Paths.get("Foo.tmp"), StandardOpenOption.DELETE_ON_CLOSE);
Respuesta larga
Después de investigar mucho, encontré que Java NIO tiene una forma de eliminar al salir, pero se acerca a ella de una forma diferente a la de la E / S de Java.
En primer lugar, el Javadoc para Files.createTempFile()
describe tres formas de eliminar archivos:
Cuando se usa como archivos de trabajo [sic], el archivo resultante se puede abrir utilizando la opción StandardOpenOption.DELETE_ON_CLOSE para que el archivo se elimine cuando se invoca el método de cierre apropiado. Alternativamente, se puede usar un shutdown-hook o el mecanismo
File.deleteOnExit()
para eliminar el archivo automáticamente.
La última opción, File.deleteOnExit()
es, por supuesto, un método de E / S de Java, que estamos tratando de evitar. Un gancho de apagado es lo que está sucediendo detrás de escena cuando se llama al método mencionado anteriormente. Pero la opción DELETE_ON_CLOSE
es Java NIO puro.
En lugar de eliminar archivos arbitrarios, Java NIO asume que solo está interesado en eliminar los archivos que realmente está abriendo. Por lo tanto, los métodos que crean una nueva secuencia como Files.newOutputStream()
pueden tomar opcionalmente varias OpenOptions
, donde puede ingresar StandardOpenOption.DELETE_ON_CLOSE
. Lo que hace es eliminar el archivo tan pronto como se cierre la transmisión (ya sea por una llamada a .close()
o la salida de JVM).
Por ejemplo:
Files.newOutputStream(Paths.get("Foo.tmp"), StandardOpenOption.DELETE_ON_CLOSE);
... eliminará el archivo asociado con la secuencia cuando la secuencia se cierre, ya sea desde una llamada explícita a .close()
, la secuencia se cerrará como parte de una declaración de intentar con recursos, o la terminación de la JVM.
Actualización: en algunos sistemas operativos como Linux, StandardOpenOption.DELETE_ON_CLOSE
elimina tan pronto como se crea el OutputStream. Si todo lo que necesita es un OutputStream, todavía puede estar bien. Ver DELETE_ON_CLOSE elimina archivos antes de cerrar en Linux para obtener más información.
Por lo tanto, Java NIO agrega una nueva funcionalidad a través de Java I / O, ya que puede eliminar un archivo al cerrar una secuencia. Si esa es una alternativa lo suficientemente buena para eliminar durante la salida JVM, puede hacerlo en Java NIO puro. De lo contrario, tendrá que confiar en el File.deleteOnExit()
Java I / O o en un gancho de cierre para eliminar el archivo.
Detrás de escena, File.deleteOnExit()
solo creará un shutdown hook
través de Runtime.addShutdownHook()
.
Entonces, puedes hacer lo mismo con NIO:
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
Path path = ...;
Files.delete(path);
}
});
No sugeriría el uso de StandardOpenOption.DELETE_ON_CLOSE
en un reemplazo para File.deleteOnExit()
. Como se menciona en la documentación, no pretende ser de propósito general ni es probable que funcione correctamente fuera de los casos triviales.
DELETE_ON_CLOSE
, como su nombre lo indica, está diseñado para ser usado para eliminar un archivo una vez que se cierra para limpiar de inmediato un recurso que ya no se necesita. La documentación para Files.createTempFile()
es igualmente clara en este punto, DELETE_ON_CLOSE
se puede usar para "archivos de trabajo" solo necesarios mientras el archivo está abierto.
Los documentos Files.createTempFile()
sugieren directamente escribir su propio gancho de apagado o simplemente continuar usando File.deleteOnExit()
. A pesar de su deseo de utilizar NIO, no hay nada intrínsecamente incorrecto en el uso de File.deleteOnExit()
si solo está trabajando con el sistema de archivos local. Si no está utilizando (o no está seguro de estar usando) el sistema de archivos local y, por lo tanto , no puede usar File.deleteOnExit()
, es lo suficientemente sencillo como para escribir su propio gancho de cierre tal como lo hace el File
:
public final class DeletePathsAtShutdown {
private static LinkedHashSet<Path> files = new LinkedHashSet<>();
static {
Runtime.getRuntime().addShutdownHook(
new Thread(DeletePathsAtShutdown::shutdownHook));
}
private static void shutdownHook() {
LinkedHashSet<Path> local;
synchronized {
local = paths;
paths = null;
}
ArrayList<Path> toBeDeleted = new ArrayList<>(theFiles);
Collections.reverse(toBeDeleted);
for (Path p : toBeDeleted) {
try {
Files.delete(p);
} catch (IOException | RuntimeException e) {
// do nothing - best-effort
}
}
}
public static synchronized void register(Path p) {
if (paths == null) {
throw new IllegalStateException("ShutdownHook already in progress.");
}
paths.add(p);
}
}
Claro, podría ser bueno si NIO incluyera un gancho de apagado similar fuera de la caja, pero su ausencia no es razón para usar la herramienta incorrecta para el trabajo. También puede agregar más funcionalidad a DeletePathsAtShutdown
, como una función remove()
, o soporte para eliminar directorios .