【问题标题】:Odd behaviour when deleting Files with Files.delete()使用 Files.delete() 删除文件时的奇怪行为
【发布时间】:2015-10-14 21:59:56
【问题描述】:

请考虑以下示例 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 方法时,它会产生以下输出:

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 没有正确反映这一点。

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

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,可以试试打印父目录的权限吗?
  • 顺便说一句,你可以直接使用Files.copy()InputStream的内容复制到Path中;不需要 IOUtils
  • 尝试在Files.delete(file); 上设置一个断点,然后跳过它。在那一刻,文件是否真的从资源管理器窗口中消失了?我怀疑删除以某种方式成功,但 Windows 保留了该文件,因为有另一个引用打开它。这可能是 Windows 工作方式中的低级别。我不希望在 Posix(例如:Linux)系统上出现同样的情况。
  • 查看 dhke 答案的最后两个 cmets。他的,然后是我的。

标签: java windows file-io


【解决方案1】:

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

【讨论】:

  • 但它不应该抛出一个 IO / 访问异常吗? - 我的意思是现在它说的是okay, deleted it,但实际上并没有删除它。关闭输出流的那一刻就发生了删除。所以理论上你可以在不知道最后会发生删除的情况下继续写内容......
  • @dognose 因为文件name不见了,即删除成功。但是,该文件仍然存在(直到您关闭它的最后一个句柄)。 Windows 应用程序经常让您相信您无法删除打开的文件,但实际上并非如此。
【解决方案2】:

可以删除打开的文件吗?

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

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

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

去看看...

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

游览:Windows

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

CreateFile():

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

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

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

DeleteFile():

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

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

现在的问题是:

Files.newOutputStream() 是否设置了FILE_SHARE_DELETE

查看the source,您可以看到shareDelete 确实默认为true。重置它的唯一方法是使用非标准的ExtendedOpenOptionNOSHARE_DELETE

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

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

这个问题的答案隐藏在上面DeleteFile() 的文档中:该文件仅被标记为删除,该文件仍然存在。在 Windows 上,您无法使用标记为删除的文件的名称创建文件,直到文件被正确删除,即文件的所有句柄都已关闭。

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

为什么Files.exists() 返回false

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

详细说明:Java 代码在没有参数的情况下调用 FileSystemProvider.checkAccess()),它调用 WindowsFileSystemProvider.checkReadAccess(),它立即尝试打开文件并因此失败。据我所知,这是您拨打Files.exist()时的路径。

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

猜想,我会说GetFileAttributeEx() 在某些时候调用GetFileInformationByHandle(),它永远不会到达,因为它一开始就无法获得文件句柄。

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

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

欢迎在您的脑海中出现更多奇怪的循环。

【讨论】:

  • 我不相信你关于这里的 Windows 实现及其与 linux 的相似性的论点。您的手动参考是关于打开权限而不是实际删除操作是否会成功。我需要看一个低级示例(用 C 或类似语言编写)来证明这一点。通常,windows 在名称和文件之间的联系比 POSIX 系统强得多。如果名称不存在,文件通常不会存在于 Windows 上。
  • @couling 在我证明一个茶壶之前,你认为FILE_SHARE_DELETE 是干什么用的?
  • 是手册不清楚的错。抱歉,我很迂腐,但它说的是Otherwise, other processes cannot open the file or device if they request delete access. 这是在谈论“打开”功能而不是“删除”功能,并且没有明确说明在文件在其他地方打开时可能会发生删除。
  • 确实如此。很好地挖掘它......所以在最后一个句柄关闭之前文件不会被“删除”......好吧,OP的代码将保持一个句柄打开(来自同一个程序)。删除会成功,因为这样做没有错误,但文件不会被删除,直到 OutputStream 因为打开的句柄而关闭。相当准确地解释了这种行为。我的理解是,与 Posix 不同,在 Windows 中删除文件不会删除名称,直到内容消失。
  • @davmac 这完全取决于 Windows。 File.exists() 很可能基于GetFileAttribtes,如果文件被标记为删除或文件不存在,则会失败。似乎java遇到了错误,只是假设它不存在。 msdn.microsoft.com/en-us/library/windows/desktop/…
猜你喜欢
  • 2018-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-03
  • 2017-11-20
  • 2013-10-04
相关资源
最近更新 更多