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

请考虑下面的示例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,然后尝试删除文件, 而不先closuresstream 。 这是我原来的问题,当然也是错误的,但是会导致一些奇怪的观察。

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()不会引发exception?
  • 为什么以下调用Files.exist()返回false?
  • 为什么不可能重新创build文件?

关于最后一个问题,我注意到当你停在断点1时,文件在资源pipe理器中仍然可见。当你终止JVM时,文件将被删除。 closuresstreamdeleteAndCheck()后按预期工作。

在我看来,在closuresstream之前,删除不会传播到操作系统,并且“文件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> 

更新澄清

该文件在Windows资源pipe理器中消失,如果streamclosures且Files.delete()被调用 – 最后一个操作触发 – 或者调用了Files.delete()而没有closuresstream并且JVM终止。

你可以删除一个打开的文件?

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

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

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

去搞清楚 …

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

游览:Windows

有必要在Windows上指定标志使大多数人认为Windows不能删除打开的文件,但事实并非如此。 这只是默认行为。

Windows API文档似乎(故意地)模糊了这个过程,事实并没有真正改善:

CreateFile()

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

否则,其他进程如果请求删除访问,则不能打开该文件或设备。

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

DeleteFile()

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

对没有名字的文件打开句柄是创建未命名的临时文件最典型的方法之一:创建一个新文件,打开它,删除文件。 你现在有一个文件的句柄,没人可以打开。 在Unix上,文件名确实消失了,在Windows上你不能打开同名的文件。

现在的问题是:

Files.newOutputStream()是否设置FILE_SHARE_DELETE

看着源代码 ,你可以看到shareDelete默认值是true 重置它的唯一方法是使用非标准的ExtendedOpenOption NOSHARE_DELETE

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

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

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

混合名称删除和实际文件删除的混淆可能是为什么Windows首先不允许删除打开的文件。

为什么Files.exists()返回false

在Windows的深处的Files.exists()某些时候打开该文件,而且我们已经知道我们不能在Windows上重新打开一个已删除但仍然打开的文件

详细地说:Java代码调用FileSystemProvider.checkAccess() ),它没有参数,它调用WindowsFileSystemProvider.checkReadAccess() ,它直接试图打开文件,因此失败。 从我可以告诉,这是您调用Files.exist()时所采取的路径。

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

猜测,我会说, GetFileAttributeEx()调用GetFileInformationByHandle()在某些时刻,它永远不会得到,因为它不能得到一个文件句柄。

所以,在DeleteFile() ,文件在大多数实际应用中都没有了。 它仍然有一个名称,但是,在目录列表中显示,并且无法打开具有相同名称的文件,直到原始文件的所有句柄关闭。

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

欢迎来到你脑海中更多怪异的循环。

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