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

Mar*_*fer 21 java windows file-io

请考虑以下示例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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我写入FileOutputStream并尝试删除该文件,而不先关闭Stream.这是我原来的问题,当然是错的,但它会导致一些奇怪的观察.

在Windows 7上运行main方法时,它会生成以下输出:

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
Run Code Online (Sandbox Code Playgroud)
  • 为什么第一次调用Files.delete()不会抛出异常?
  • 为什么以下对Files.exist()的调用返回false?
  • 为什么不能重新创建文件?

关于最后一个问题,我注意到当你在断点处停止时,该文件在资源管理器中仍然可见.当你终止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>
Run Code Online (Sandbox Code Playgroud)

更新澄清

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

dhk*_*hke 25

你能删除一个打开的文件吗?

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

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

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

去搞清楚 ...

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

游览:Windows

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

Windows API文档似乎(故意?)对流程模糊不清,这种情况并没有真正得到改善:

CreateFile():

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

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

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

DeleteFile():

DeleteFile函数在关闭时标记要删除的文件.因此,在关闭文件的最后一个句柄之前不会发生文件删除.使用ERROR_ACCESS_DENIED后续调用CreateFile以打开文件失败.

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

现在的问题是:

Files.newOutputStream()是否设置FILE_SHARE_DELETE

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

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

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

答案就隐藏在DeleteFile()上面的文档中:文件只标记为删除,文件仍然存在.在Windows上,在正确删除文件之前,无法创建具有标记为删除的文件的文件,即文件的所有句柄都将关闭.

混合名称删除和实际文件删除可能会造成混淆,这可能是Windows首先禁止删除打开文件的原因.

为什么要Files.exists()回来false

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

详细说明:Java代码调用FileSystemProvider.checkAccess())没有参数,调用WindowsFileSystemProvider.checkReadAccess()哪个直接尝试打开文件因此失败.据我所知,这是你打电话时的路径Files.exist().

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

猜测,我会说在某些时候GetFileAttributeEx()调用GetFileInformationByHandle()它,它永远不会达到,因为它无法在第一时间获得文件句柄.

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

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

欢迎来到你头脑中的一些奇怪的循环.

  • 实际上确实如此.在挖掘它时做得很好......所以在最后一个句柄关闭之前文件不会被"删除"......好的OP的代码将保持一个句柄打开(来自同一个程序不少).删除将成功,因为这样做没有错误,但是由于打开句柄而关闭OutputStream,因此不会删除该文件.相当准确地解释了这种行为.我的理解是,与Posix不同,在Windows中删除文件不会删除名称,直到内容消失. (2认同)