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 有点奇怪的行为的主要内容,如果未能解决你的问题,请参考以下文章