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

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

我写入 FileOutputStream 并尝试在之后删除文件而不先关闭 Stream。这是我最初的问题,当然是错误的,但它导致了一些奇怪的观察。

当您在 Windows 7 上运行 main Method 时,它会产生以下输出:

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() 按预期工作。

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

    有人可以准确解释这里发生了什么吗?

    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 资源管理器中消失。

    最佳答案

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

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

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

    但是,有一点不同:Unix 立即删除文件名 , 而 Windows 仅在最后一个句柄关闭时删除文件名 .但是,它会阻止您打开同名文件,直到(已删除)文件的最后一个句柄关闭。

    去搞清楚 ...

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

    游览:Windows

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

    CreateFile() :

    Enables subsequent open operations on a file or device to request delete access.

    Otherwise, other processes cannot open the file or device if they request delete access.

    If this flag is not specified, but the file or device has been opened for delete access, the function fails. Note Delete access allows both delete and rename operations.



    DeleteFile() :

    The DeleteFile function marks a file for deletion on close. Therefore, the file deletion does not occur until the last handle to the file is closed. Subsequent calls to CreateFile to open the file fail with ERROR_ACCESS_DENIED.



    为无名文件打开句柄是创建未命名临时文件的最典型方法之一:创建一个新文件,打开它,删除该文件。您现在拥有了其他人无法打开的文件的句柄。在 Unix 上,文件名真的消失了,在 Windows 上你不能打开同名的文件。

    现在的问题是:

    是否Files.newOutputStream()套装FILE_SHARE_DELETE ?

    看着 the source ,你可以看到 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 资源管理器用于确定文件列表)查找文件名,但不会告诉您名称的可访问性。

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

    关于java - 使用 Files.delete() 删除文件时的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31606978/

    相关文章:

    java - 使用 JSTL 在 Map 中键和值具有 ArrayList

    java - 格式化 XML 输出/修改 apache commons-configurations2 中的 Transformer

    c++ - 只读取文本文件中的给定行?

    c++ - 在 C++ 中读取和写入 vector<bool> 到文件

    java - 如何在 Windows 上用 Java 创建然后原子地重命名文件?

    java - 在 json 模式中使用正则表达式来验证没有空格的字符串?

    Java 到 JavaScript(加密相关)

    c++ - 如何在不打开的情况下获得最大文件的大小?

    c++ - 独立、操作系统无关、架构中立、多线程库

    c++ - 使用来自系统级进程(Windows 服务)的登录用户上下文模拟和运行任何方法 :