texto - insertar/modificar/eliminar datos de un txt en java
Comportamiento extraño al eliminar archivos con archivos.delete() (2)
Considere la siguiente clase de ejemplo de Java (pom.xml a continuación):
package test.filedelete;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import org.apache.commons.io.IOUtils;
public class Main
{
public static void main(String[] args) throws IOException
{
byte[] bytes = "testtesttesttesttesttesttesttesttesttest".getBytes();
InputStream is = new ByteArrayInputStream(bytes);
Path tempFileToBeDeleted = Files.createTempFile("test", "");
OutputStream os = Files.newOutputStream(tempFileToBeDeleted);
IOUtils.copy(is, os);
deleteAndCheck(tempFileToBeDeleted);
// breakpoint 1
System.out.println("/nClosing stream/n");
os.close();
deleteAndCheck(tempFileToBeDeleted);
}
private static void deleteAndCheck(Path file) throws IOException
{
System.out.println("Deleting file: " + file);
try
{
Files.delete(file);
}
catch (NoSuchFileException e)
{
System.out.println("No such file");
}
System.out.println("File really deleted: " + !Files.exists(file));
System.out.println("Recreating deleted file ...");
try
{
Files.createFile(file);
System.out.println("Recreation successful");
}
catch (IOException e)
{
System.out.println("Recreation not possible, exception: " + e.getClass().getName());
}
}
}
Escribo en un FileOutputStream e intento eliminar el archivo sin cerrar primero el Stream . Este fue mi problema original, y por supuesto mal, pero conduce a algunas observaciones extrañas.
Cuando ejecutas el método principal en Windows 7, produce el siguiente resultado:
Deleting file: C:/Users/MSCHAE~1/AppData/Local/Temp/test6100073603559201768
File really deleted: true
Recreating deleted file ...
Recreation not possible, exception: java.nio.file.AccessDeniedException
Closing stream
Deleting file: C:/Users/MSCHAE~1/AppData/Local/Temp/test6100073603559201768
No such file
File really deleted: true
Recreating deleted file ...
Recreation successful
- ¿Por qué la primera llamada a Files.delete () no lanza una excepción?
- ¿Por qué la siguiente llamada a Files.exist () devuelve false?
- ¿Por qué no es posible crear el archivo de nuevo?
Con respecto a la última pregunta, noté que el archivo aún está visible en el Explorador cuando se detiene en el punto de interrupción 1. Cuando finaliza la JVM, el archivo se eliminará de todos modos. Después de cerrar la secuencia, deleteAndCheck () funciona como se esperaba.
Me parece que la eliminación no se propaga al sistema operativo antes de cerrar la secuencia y la API de Archivos no refleja esto correctamente.
¿Puede alguien explicar exactamente lo que está pasando aquí?
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>filedelete</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
</project>
Actualización para aclarar
El archivo desaparece en el explorador de Windows, si se cierra la secuencia Y se llama a Files.delete () (se activa la última operación), o si se ha llamado a Files.delete () sin cerrar la secuencia y se finaliza la JVM.
Si Files.delete no lanzó la excepción, significa que eliminó el archivo. Files.delete javadoc dice que "en algunos sistemas operativos puede que no sea posible eliminar un archivo cuando está abierto y en uso por esta máquina virtual Java u otros programas".
¿Se puede eliminar un archivo abierto?
Es perfectamente válido eliminar la entrada de directorio de un archivo cuando se abre el archivo. En Unix, esta es la semántica predeterminada y Windows se comporta de manera similar siempre que FILE_SHARE_DELETE
esté configurado en todos los manejadores de archivos abiertos a ese archivo.
[Editar: Gracias a @couling por discusiones y correcciones]
Sin embargo, hay una ligera diferencia: Unix elimina el nombre del archivo inmediatamente , mientras que Windows elimina el nombre del archivo solo cuando se cierra el último manejador . Sin embargo, le impide abrir un archivo con el mismo nombre hasta que se cierre el último identificador del archivo (eliminado).
Imagínate ...
Sin embargo, en ambos sistemas, eliminar el archivo no necesariamente hace que el archivo desaparezca, sigue ocupando espacio en el disco siempre que haya un identificador abierto. El espacio ocupado por el archivo solo se libera cuando se cierra el último manejador abierto.
Excursión: Windows
El hecho de que sea necesario especificar el indicador en Windows hace que a la mayoría de las personas les parezca que Windows no puede eliminar archivos abiertos, pero en realidad eso no es cierto. Eso es sólo el comportamiento predeterminado .
La situación no mejora realmente por el hecho de que la documentación de la API de Windows parece (¿deliberadamente?) Vaga en el proceso:
Habilita las operaciones de apertura posteriores en un archivo o dispositivo para solicitar el acceso de eliminación.
De lo contrario, otros procesos no pueden abrir el archivo o dispositivo si solicitan el acceso de eliminación.
Si este indicador no se especifica, pero el archivo o dispositivo se ha abierto para eliminar el acceso, la función falla. Nota Eliminar acceso permite tanto eliminar como renombrar operaciones.
La función DeleteFile marca un archivo para su eliminación al cerrar. Por lo tanto, la eliminación del archivo no se produce hasta que se cierra el último identificador del archivo. Las llamadas posteriores a CreateFile para abrir el archivo fallan con ERROR_ACCESS_DENIED.
Tener un identificador abierto en un archivo sin nombre es uno de los métodos más comunes para crear archivos temporales sin nombre: crear un nuevo archivo, abrirlo, eliminar el archivo. Ahora tiene un identificador de un archivo que nadie más puede abrir. En Unix, el nombre del archivo realmente desapareció y en Windows no puede abrir un archivo con el mismo nombre.
La pregunta es ahora:
¿ Files.newOutputStream() establece FILE_SHARE_DELETE
?
En cuanto a Files.newOutputStream() , puede ver que shareDelete
es por defecto true
. La única forma de restablecerlo es usar el ExtendedOpenOption
NOSHARE_DELETE
no estándar.
Entonces, sí, puede eliminar los archivos abiertos en Java a menos que estén explícitamente bloqueados.
¿Por qué no puedo volver a crear el archivo eliminado?
La respuesta a eso está oculta en la documentación de DeleteFile()
arriba: el archivo solo está marcado para su eliminación, el archivo todavía está allí. En Windows, no puede crear un archivo con el nombre de un archivo marcado para su eliminación hasta que el archivo se elimine correctamente , es decir, todos los identificadores del archivo están cerrados.
La posible confusión de mezclar la eliminación de nombres y la eliminación de archivos reales es probablemente la razón por la que Windows no permite eliminar archivos abiertos de forma predeterminada en primer lugar.
¿Por qué Files.exists()
devuelve false
?
Files.exists()
en el final profundo de Windows abre ese archivo en algún momento y ya sabemos que no podemos volver a abrir un archivo eliminado pero aún abierto en Windows .
En detalle: el código Java llama a FileSystemProvider.checkAccess()
) sin argumentos, lo que llama a WindowsFileSystemProvider.checkReadAccess()
que inmediatamente intenta abrir el archivo y, por lo tanto, falla. Por lo que puedo decir, esta es la ruta tomada cuando llama a Files.exist()
.
También hay otra ruta de código que llama a GetFileAttributeEx()
para recuperar los atributos del archivo. Una vez más, no se documenta lo que sucede cuando intenta recuperar los atributos de un archivo eliminado pero aún no eliminado , pero de hecho, no puede recuperar los atributos de archivo de un archivo marcado para su eliminación .
Adivinando, diría que GetFileAttributeEx()
llama a GetFileInformationByHandle()
en algún punto, al que nunca llegará porque no puede obtener un identificador de archivo en primer lugar.
Así que, de hecho, después de DeleteFile()
el archivo desaparece por motivos prácticos. Todavía tiene un nombre, sin embargo, aparece en las listas de directorios y no puede abrir un archivo con el mismo nombre hasta que el archivo original haya cerrado todos sus controladores.
Este comportamiento es más o menos coherente, porque el uso de GetFileAttributes()
para verificar si un archivo existe es en realidad una verificación de accesibilidad del archivo , que se interpreta como un archivo existente . FindFirstFile()
(usado por el Explorador de Windows para determinar la lista de archivos) encuentra los nombres de los archivos pero no le dice nada sobre la accesibilidad de los nombres.
Bienvenido a unos cuantos bucles más extraños en tu cabeza.