删除带有文件的文件的奇怪行为。delete()

2022-09-01 15:45:24

请考虑以下示例 Java 类 (pom.xml如下所示):

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());
        }
    }
}

我写入文件输出流,然后尝试删除该文件,而无需先关闭流。这是我最初的问题,当然是错误的,但它导致了一些奇怪的观察结果。

当您在 Windows 7 上运行主方法时,它将生成以下输出:

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
  • 为什么第一次调用 Files.delete() 时不会引发异常?
  • 为什么以下对 Files.exist() 的调用返回 false?
  • 为什么无法重新创建文件?

关于最后一个问题,我注意到当您在断点1处停止时,该文件在资源管理器中仍然可见。当您终止JVM时,该文件仍将被删除。关闭流删除后,AndCheck() 按预期工作。

在我看来,在关闭流之前,删除操作不会传播到操作系统,并且文件 API 没有正确反映这一点。

有人能解释一下这里到底发生了什么吗?

啪.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>

澄清更新

如果关闭流并调用 Files.delete() - 触发最后一个操作 - 或者如果在未关闭流的情况下调用了 Files.delete() 并且终止了 JVM,则文件将在 Windows 资源管理器中消失。


答案 1

您可以删除打开的文件吗?

打开文件时删除文件的目录条目是完全有效的。在Unix中,这是默认的语义,只要在对该文件打开的所有文件句柄上设置FILE_SHARE_DELETE,Windows的行为就类似。

[编辑:感谢@couling的讨论和更正]

但是,有一个细微的区别:Unix会立即删除文件名,而Windows仅在最后一个句柄关闭时才删除文件名。但是,它会阻止您打开具有相同名称的文件,直到(已删除的)文件的最后一个句柄关闭。

去图...

但是,在这两个系统上,删除文件并不一定会使文件消失,只要它仍然有一个打开的句柄,它仍然占用磁盘上的空间。仅当最后一个打开的句柄关闭时,才会释放文件占用的空间。

游览:窗户

有必要在Windows上指定标志,这使得大多数人认为Windows无法删除打开的文件,但实际上并非如此。这只是默认行为。

创建文件()

在文件或设备上启用后续打开操作以请求删除访问权限。

否则,如果其他进程请求删除访问权限,则无法打开文件或设备。

如果未指定此标志,但已打开文件或设备进行删除访问,则该功能将失败。注意:删除访问权限允许删除和重命名操作。

删除文件()

删除文件函数将文件标记为在关闭时删除。因此,在关闭文件的最后一个句柄之前,不会发生文件删除。对 CreateFile 以打开该文件的后续调用失败,并显示ERROR_ACCESS_DENIED。

对没有名称的文件打开句柄是创建未命名临时文件的最典型方法之一:创建新文件,打开它,删除该文件。现在,您拥有了其他人无法打开的文件的句柄。在Unix上,文件名确实消失了,在Windows上,您无法打开具有相同名称的文件。

现在的问题是:

Files.newOutputStream() 是否设置FILE_SHARE_DELETE

查看源代码,您可以看到确实默认为 。重置它的唯一方法是使用非标准NOSHARE_DELETEshareDeletetrueExtendedOpenOption

所以,是的,您可以在Java中删除打开的文件,除非它们被显式锁定。

为什么我无法重新创建已删除的文件?

答案隐藏在上面的文档中:该文件仅标记为删除,该文件仍然存在。在 Windows 上,在正确删除文件(即文件的所有句柄关闭)之前,您无法创建具有标记为删除的文件名称的文件。DeleteFile()

混合名称删除和实际文件删除的可能混淆可能是Windows首先默认不允许删除打开的文件的原因。

为什么 Files.exists() 返回 false

Files.exists()在Windows的深处,在某个时候打开该文件,我们已经知道我们无法在Windows上重新打开已删除但仍然打开的文件

详细内容:Java代码调用FileSystemProvider.checkAccess()),没有参数,它调用WindowsFileSystemProvider.checkReadAccess(),它立即尝试打开文件,因此失败。据我所知,这是您致电时所采用的路径。Files.exist()

还有另一个代码路径调用 GetFileAttributeEx() 来检索文件属性。同样,当您尝试检索已删除但尚未删除的文件的属性时,不会记录会发生什么情况,但实际上,您无法检索标记为删除的文件的文件属性。

猜测,我会说在某个时候调用GetFileInformationByHandle(),它永远不会得到,因为它首先无法获得文件句柄。GetFileAttributeEx()

因此,确实,在文件出于大多数实际目的而消失之后。但是,它仍然具有一个名称,显示在目录列表中,并且在原始文件关闭其所有句柄之前,您无法打开具有相同名称的文件。DeleteFile()

此行为或多或少是一致的,因为用于检查文件是否存在实际上是文件可访问性检查,它被解释为文件存在FindFirstFile()(由 Windows 资源管理器用于确定文件列表)查找文件名,但不会告诉您有关名称的可访问性的任何信息。GetFileAttributes()

欢迎来到你脑海中一些更奇怪的循环。


答案 2

如果 Files.delete 没有引发异常,则表示它删除了该文件。Files.delete javadoc表示,“在某些操作系统上,当文件打开并由此Java虚拟机或其他程序使用时,可能无法删除该文件”。