当前位置: 首页 > 面试题库 >

使用Files.delete()删除文件时的奇怪行为

东方宜
2023-03-14
问题内容

请考虑以下示例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());
        }
    }
}

我写一个FileOutputStream,然后尝试删除该文件, 而不先关闭Stream 。这是我最初的问题,当然是错误的,但它导致了一些奇怪的发现。

在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时,无论如何该文件都会被删除。关闭流后,deleteAndCheck()会按预期工作。

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

有人可以确切说明这里发生了什么吗?

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>

更新以澄清

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


问题答案:

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

打开文件时删除html" target="_blank">文件的目录条目是完全有效的。在Unix中,这是默认的语义,并且Windows
FILE_SHARE_DELETE在打开该文件的所有文件句柄上进行设置时的行为类似。

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

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

去搞清楚 …

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

游览:Windows

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

CreateFile()

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

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

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

DeleteFile()

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

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

现在的问题是:

是否
设置Files.newOutputStream()FILE_SHARE_DELETE

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

因此,是的,您可以删除Java中打开的文件,除非它们被明确锁定。

为什么不能重新创建已删除的文件?

DeleteFile()上面的文档中隐藏了答案:该文件仅标记为删除,该文件仍然存在。在Windows上,只有 正确
删除文件(即关闭文件的所有句柄)后,才能使用标记为删除的文件 创建文件。 __

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

为什么要Files.exists()退货false

Files.exists()在Windows的最深层中
,“某个时刻”会打开该文件,我们已经知道我们无法在Windows上重新打开已删除但仍打开的文件

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

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

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

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

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

欢迎您再来几个怪异的循环。



 类似资料:
  • 参考Symfony 2关于文件上传的Cookbook,我尝试使用Doctrine的@PostRemove事件监听器在文件从数据库中删除后删除文件。 Document.php File.php 数据库: 当我删除ID为1的文档时,不知何故学说也解除关联ID为1的文件。 据我所知,这些步骤导致了这种奇怪的行为: 1-条令的将调用方法,该方法反过来调用 2-在中,参与者根据文档的id删除了文档的文件,然

  • 问题内容: @Entity public class Person { 给定以下类结构,当我尝试将新位置添加到“人的位置”列表中时,它总是导致以下SQL查询: 和 Hibernate(3.5.x / JPA 2)删除给定Person的所有关联记录,然后重新插入所有以前的记录以及新的记录。 我有一个想法,即Location上的equals / hashcode方法可以解决此问题,但是它没有任何改变。

  • 在我的应用程序中,左侧有一个,我根据中的选择更新右侧的窗格。一个非常直接的场景。当选择为空时,我会在窗格中显示类似“请进行选择”的消息,即我也会在 ,以防用户单击某些。侦听器还附加到所选的的,以防用户没有单击某些项目,但所选项目的值因某种原因而更改。用户可以通过单击其中的删除按钮来删除特定项目。在任何时候,用户可以点击更新按钮来更新显示当前选择的标签内容,以防我的处理程序错过了一些事件。 在这个简

  • 我正在使用ListView。向ListView添加新项很容易,但如何删除? 我通过以下方式构建ListView: Listcell Y的一个实例在setGraphics中设置了一个标签。要从我的观察列表中删除与之关联的字符串,我添加了一个MouseEvent处理程序: 虽然这段代码通过从ObservableList中删除每个单击的项目来工作,但listView的行为很奇怪。每次单击标签时,它都会成

  • 我已经成功地使用Apache POI库读取了excel文件。但是,我从它那里得到了一个奇怪的行为,我不知道为什么它会发生。 如果我创建一个新的excel文件,并对所需数据进行如下调整: 设置在电子邮件列第一个的空单元格根本不被读取(忽略)。 下面是我用来阅读excel文件的完整代码:

  • 我正在使用Mapstruct映射将一个POJO转换为另一个POJO模型 以下是mapstruct自动生成的方法 该方法基本上获取源POJO的映射,并将其转换为目标模型的映射。生成正在通过。 当我运行代码时,我在这个方法中得到了ClassCast异常:HeaderAttributeGenericDataTypeMaptoStringEnergiectAttributeDataMap 堆栈跟踪: 我还