Files.delete 和 Files.deleteIfExists 有点奇怪的行为

Posted

技术标签:

【中文标题】Files.delete 和 Files.deleteIfExists 有点奇怪的行为【英文标题】:A bit strange behaviour of Files.delete and Files.deleteIfExists 【发布时间】:2018-06-26 22:23:13 【问题描述】:

我得到了这样的代码:

paths.forEach(folderPath -> 
        Path to = folderPath.getRoot().resolve(folderPath.getParent().subpath(0, folderPath.getNameCount() - 1)); // До имени (исключительно)
        try 
            Files.list(folderPath).forEach(filePath -> 
                try  Files.move(filePath, to.resolve(filePath.getFileName()), StandardCopyOption.ATOMIC_MOVE); 
                catch (IOException e)  processException(e); 
            );
            if (Files.list(folderPath).count() == 0)
                Files.deleteIfExists(folderPath); // this call
         catch (IOException e)  processException(e); 
    );

在我调用删除方法后,我的空目录被锁定(在它被调用后,检查它),但直到应用程序关闭才被删除。我觉得有点奇怪,但想知道为什么会这样。

(我使用的是 Windows 10)

【问题讨论】:

与 javafx 无关 - 或者我错过了什么? 锁定我的空目录 您可能想解释一下您在这里指的是哪种锁定?这种行为是 Java-9 特有的吗? @kleopatra 我在JavaFX项目中使用它,所以提到它以防万一。 @nullpointer 没有检查。我在 9.0.1 上运行它 我的情况下的锁定意味着该目录仍然可见,但是当我尝试对它做一些事情时(例如查看内容),它给了我一个警告,我无法访问它。 @Holger 将文件移动到当前目录的父目录的路径。移动所有文件后,程序删除目录 【参考方案1】:

来自Files.list(Path)的文档:

此方法必须在 try-with-resources 语句或类似的控制结构中使用,以确保在流的操作完成后立即关闭流的打开目录。

您没有这样做,因此Files.deleteIfExists(…) 的以下部分适用:

在某些操作系统上,当此 Java 虚拟机或其他程序打开并使用该文件时,可能无法删除该文件。

你应该使用

paths.forEach(folderPath -> 
    Path to = folderPath.getParent();
    try 
        try(Stream<Path> files = Files.list(folderPath)) 
            files.forEach(filePath -> 
                tryFiles.move(filePath, to.resolve(filePath.getFileName()), ATOMIC_MOVE);
                catch (IOException e)  processException(e); 
            );
        
        try 
            Files.deleteIfExists(folderPath);
         catch(DirectoryNotEmptyException ex) 
            // may happen as you continue when Files.move fails,
            // but you already reported the original exception then
        
     catch (IOException e)  processException(e); 
);

这会在尝试删除目录之前关闭文件流。请注意,第二个流操作已被删除,这种预检查是浪费的,当所有move 操作成功时应该不需要。但是如果其他应用程序同时插入一个新文件,则无法保证在您的Files.list(folderPath).count() == 0 检查和随后的deleteIfExists 调用之间不会发生这种情况。

更简洁的解决方案是记住 move 失败的时间。当没有move 失败时,仍然不是空的目录应被视为错误情况,应像任何其他错误一样报告,例如

paths.forEach(folderPath -> 
    Path to = folderPath.getParent();
    try 
        boolean allMovesSucceeded;
        try(Stream<Path> files = Files.list(folderPath)) 
          allMovesSucceeded = files
            .map(filePath -> 
                try 
                    Files.move(filePath, to.resolve(filePath.getFileName()), ATOMIC_MOVE);
                    return true;
                
                catch(IOException e)  processException(e); return false; 
            ).reduce(Boolean.TRUE, Boolean::logicalAnd);

        
        if(allMovesSucceeded) Files.deleteIfExists(folderPath);
     catch (IOException e)  processException(e); 
);

【讨论】:

我很惊讶,但是您的修复以及如果从 try-with-resources 中提取删除代码的工作方式相同 - 使用锁定目录。 实际上,我的意图是直接在 deleteIfExists 之前释放流,在 try-with-resources 之后删除,但我混合了范围。但即使使用错误的范围,在调用 deleteIfExists 时目录也会被锁定,但在 try-with-resources 块结束时关闭流时会立即被删除,这仍然比在原始代码中要早得多在 JVM 退出时关闭(或在执行此代码后的某个未指定时间由垃圾收集器关闭)。 包装了所有其他 .list 和 .find 调用,它的工作原理

以上是关于Files.delete 和 Files.deleteIfExists 有点奇怪的行为的主要内容,如果未能解决你的问题,请参考以下文章

017 Files的常用方法都有哪些?

NIO--FileLock,Path,Files,AsynchronousFileChannel,Charset

如何在 Java 中删除目录内容? [复制]

Mockito mockStatic 无法解析符号

删除后端的所有存储文件

如何删除其他用户拥有的文件