使用Files.delete()删除文件时出现奇怪的行为 [英] Odd behaviour when deleting Files with Files.delete()

查看:2957
本文介绍了使用Files.delete()删除文件时出现奇怪的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下示例Java类(下面的pom.xml):
$ b

  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;
$ b $ public class main
{
public static void main(String [] args)throws IOException
{
byte [] bytes =testtesttesttesttesttesttesttesttesttesttest。的getBytes();
InputStream is = new ByteArrayInputStream(bytes);

Path tempFileToBeDeleted = Files.createTempFile(test,);
OutputStream os = Files.newOutputStream(tempFileToBeDeleted);
IOUtils.copy(is,os);

deleteAndCheck(tempFileToBeDeleted);

//断点1
System.out.println(\\\
Closing stream \\\
);

os.close();

deleteAndCheck(tempFileToBeDeleted);


private static void deleteAndCheck(Path file)throws IOException
{
System.out.println(Deleting file:+ file);
尝试
{
Files.delete(file);

catch(NoSuchFileException e)
{
System.out.println(No such file);

System.out.println(File really deleted:+!Files.exists(file));

System.out.println(重新创建已删除的文件...);
尝试
{
Files.createFile(file);
System.out.println(娱乐成功);
$ b $ catch(IOException e)
{
System.out.println(Recreation not possible,exception:+ e.getClass()。getName());





我写入FileOutputStream和尝试先删除文件,而不先关闭Stream 。这是我原来的问题,当然也是错误的,但是会导致一些奇怪的现象。

在Windows 7上运行主方法时,它会产生以下输出: / p>

 删除文件:C:\ Users \ MSCHAE〜1 \AppData\Local\\ \\Temp\test6100073603559201768 
文件真的被删除:true
正在重新创建被删除的文件...
不可能休闲,异常:java.nio.file.AccessDeniedException

关闭流

删除文件:C:\ Users \ MSCHAE〜1 \AppData\Local\Temp\test6100073603559201768
没有这样的文件
文件真的被删除:true
重新创建已删除的文件...
娱乐成功




  • 为什么第一次调用Files.delete()不会引发异常?

  • 为什么以下调用Files.exist()返回false?
  • 为什么不能重新创建文件?



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



在我看来,删除没有传播到操作系统关闭流和文件API不反映这是正确的。



有人可以解释到底发生了什么吗?
$ b

pom.xml

< project xmlns =http://maven.apache.org/POM/ 4.0.0xmlns: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>
<依赖>
< groupId> commons-io< / groupId>
< artifactId> commons-io< / artifactId>
< version> 2.4< / version>
< / dependency>
< /依赖关系>
< / project>

澄清更新



文件在Windows资源管理器中消失,如果流关闭,则调用Files.delete() - 最后一个操作触发 - 或者调用Files.delete()而不关闭流,JVM是终止。

解决方案

您可以删除打开的文件吗? b $ b

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

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



去图...

走开,只要还有一个开放的句柄,它仍然占用磁盘上的空间。



Excursion:Windows


$文件占用的空间仅在上次打开的句柄关闭时才会释放。 b $ b

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



这种情况并没有真正改善,因为Windows API文档似乎(故意的) :



CreateFile()
$ b


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



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

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


的DeleteFile()


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

打开无名文件句柄是最重要的创建无名临时文件的典型方法:创建一个新文件,打开它,删除文件。你现在有一个文件的句柄,没人可以打开。在Unix上,文件名是真的没有了,在Windows上,你不能打开一个同名的文件。

现在的问题是:



是否 Files.newOutputStream() set FILE_SHARE_DELETE



查看来源,你可以看到 shareDelete 的确默认为。重置它的唯一方法是使用非标准的 ExtendedOpenOption NOSHARE_DELETE



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



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



上面的答案隐藏在 DeleteFile()该文件只被标记为删除,该文件仍然在那里。在Windows上,你不能创建一个名为 的文件,这个文件被标记为删除,直到文件正确被删除,即文件的所有句柄都关闭。



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



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



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



详细信息:Java代码调用 FileSystemProvider.checkAccess() ),没有参数,它调用 WindowsFileSystemProvider.checkReadAccess() 直接试图打开文件,因此失败。从我所知道的情况来看,这是您调用 Files.exist()时所采取的路径。



另一个代码路径调用 GetFileAttributeEx() 检索文件属性。再一次,没有记录当您尝试检索已删除但尚未删除的文件的属性时会发生什么情况,但事实上,您无法检索标记为删除的文件的文件属性
$ b 猜测,我会说 GetFileAttributeEx()调用 GetFileInformationByHandle() 在某个时刻,它永远不会得到,因为它不能得到一个文件句柄。

所以确实,在 DeleteFile ()这个文件对于大多数实际的目的都没有了。它仍然有一个名称,但是,在目录列表中显示,并且无法打开具有相同名称的文件,直到原始文件的所有句柄关闭。这个行为或多或少一致,因为使用 GetFileAttributes()来检查一个文件是否存在$ b $ em文件的辅助功能检查,被解释为文件存在 FindFirstFile()名称,但没有告诉你关于名称的可访问性

欢迎来到更多奇怪的循环。


Please consider the following example Java class (pom.xml below):

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

I write to a FileOutputStream and try to delete the file afterwards without closing the Stream first. This was my original problem, and of course wrong, but it leads to some strange observations.

When you run the main Method on Windows 7 it produces the following output:

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

  • Why does the first call to Files.delete() not throw an exception?
  • Why does the following call to Files.exist() return false?
  • Why is it not possible to create the file anew?

Regarding the last question I noticed that the file is still visible in the Explorer when you stop at breakpoint 1. When you terminate the JVM then, the file will be deleted anyway. After closing the stream deleteAndCheck() works as expected.

It seems to me that the deletion is not propagated to the OS before closing the stream and the Files API does not reflect this properly.

Can someone explain exactly what's happening here?

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>

Update for clarification

The file disappears in the Windows explorer, if the stream is closed AND Files.delete() is called - the last operation triggers -, or if Files.delete() has been called without closing the stream and the JVM is terminated.

解决方案

Can you delete an open file?

It is perfectly valid to delete the directory entry of a file when the file is opened. In Unix this is the default semantics and Windows behaves similarily as long as FILE_SHARE_DELETE is set on all file handles open to that file.

[Edit: Thanks to @couling for discussions and corrections]

However, there is a slight difference: Unix deletes the file name immediately, while Windows deletes the file name only when the last handle is closed. It however, prevents you from opening a file with the same name until the last handle to the (deleted) file is closed.

Go figure ...

On both systems, however, deleting the file does not necessarily make the file go away, it still occupies space on the disk as long as there is still an open handle to it. Space occupied by the file is only released when the last open handle is closed.

Excursion: Windows

That it is necessary to specify the flag on Windows makes it seem to most people that Windows cannot delete open files, but that's actually not true. That's just the default behaviour.

The situation is not really improved by the fact that Windows API documentation seems (deliberately?) vague on the process:

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.

Having an open handle to a file with no name is one of the most typical methods of creating unnamed temporary files: Create a new file, open it, delete the file. You now have a handle to a file that nobody else can open. On Unix, the filename is truly gone and on Windows you cannot open a file with the same name.

The question is now:

Does Files.newOutputStream() set FILE_SHARE_DELETE?

Looking at the source, you can see that shareDelete indeed defaults to true. The only way to reset it is to use the non-standard ExtendedOpenOption NOSHARE_DELETE.

So yes, you can delete opened files in Java unless they are explicitly locked.

Why can't I re-create the deleted file?

The answer to that is hidden in the documentation of DeleteFile() above: The file is only marked for deletion, the file is still there. On Windows, you cannot create a file with the name of a file marked for deletion until the file is properly deleted, i.e. all handles to the file are closed.

The possible confusion of mixing name deletion and actual file deletion is probably why Windows disallows deleting open files by default in the first place.

Why does Files.exists() return false?

Files.exists() in the deep end on Windows opens that file at some point and we already know that we cannot re-open a deleted-but-still-open file on Windows.

In detail: Java code calls FileSystemProvider.checkAccess()) with no arguments, which calls WindowsFileSystemProvider.checkReadAccess() which straight away tries to open the file and hence fails. From what I can tell, this is the path taken when you call Files.exist().

There is also another code path that calls GetFileAttributeEx() to retrieve file attributes. Once again, it is not documented what happens when you try to retrieve the attributes of an deleted but not yet removed file, but indeed, you cannot retrieve the file attributes of a file marked for deletion.

Guessing, I'd say that GetFileAttributeEx() calls GetFileInformationByHandle() at some point, which it will never get to because it cannot get a file handle in the first place.

So indeed, after DeleteFile() the file is gone for most practical purposes. It still has a name, however, shows up in directory listings and you cannot open a file with the same name until the original file had all its handles closed.

This behaviour is more or less consistent, because using GetFileAttributes() to check if a file exists is a actually an file accessibility check, which is interpreted as file exists. FindFirstFile() (used by Windows Explorer to determine the file list) finds file names but tells you nothing about accessibility of the names.

Welcome to a few more weird loops in your head.

这篇关于使用Files.delete()删除文件时出现奇怪的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆