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

时间:2015-07-24 09:31: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());
        }
    }
}

我写入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?
  • 为什么无法重新创建文件?

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

2 个答案:

答案 0 :(得分:23)

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

打开文件时删除文件的目录条目是完全有效的。在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。重置它的唯一方法是使用非标准ExtendedOpenOption NOSHARE_DELETE

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

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

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

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

为什么Files.exists()会返回false

Windows 深层次的

Files.exists()会在某个时刻打开该文件,我们已经知道我们无法在Windows上重新打开已删除但仍然打开的文件。 / p>

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

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

猜测,我说GetFileAttributeEx()在某个时刻调用了GetFileInformationByHandle(),这是永远无法获得的,因为它无法在第一时间处理文件句柄。

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

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

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

答案 1 :(得分:2)

如果Files.delete没有抛出异常,则表示删除了该文件。 Files.delete javadoc说&#34;在某些操作系统上,当文件被打开并被这个Java虚拟机或其他程序使用时,可能无法删除文件。