草庐IT

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

coder 2023-05-18 原文

请考虑以下示例 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 - 使用 Files.delete() 删除文件时的奇怪行为的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    5. ruby - 在 Ruby 中使用匿名模块 - 2

      假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

    6. ruby - 其他文件中的 Rake 任务 - 2

      我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

    7. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

      我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

    8. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

      我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

    9. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    10. ruby-on-rails - Rails 3 中的多个路由文件 - 2

      Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

    随机推荐