Java.nio:最简洁的递归目录删除
Posted
技术标签:
【中文标题】Java.nio:最简洁的递归目录删除【英文标题】:Java.nio: most concise recursive directory delete 【发布时间】:2016-06-29 12:43:07 【问题描述】:我目前正在尝试以递归方式删除一个目录...奇怪的是,我能找到的最短的代码片段是以下构造,它使用 ad-hoc 内部类 并在 访客模式...
Path rootPath = Paths.get("data/to-delete");
try
Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>()
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
System.out.println("delete file: " + file.toString());
Files.delete(file);
return FileVisitResult.CONTINUE;
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException
Files.delete(dir);
System.out.println("delete dir: " + dir.toString());
return FileVisitResult.CONTINUE;
);
catch(IOException e)
e.printStackTrace();
来源:here
考虑到新的nio
API 消除了如此多的混乱和样板,这感觉非常笨拙和冗长......
有没有更短的方法来实现强制的递归目录删除?
我正在寻找纯原生 Java 1.8 方法,所以请不要链接到外部库...
【问题讨论】:
这感觉非常笨拙和冗长 为什么?这是一个非常好的方法。而 Java 8Files.walk
不会给你这样做的机会。
因为这会迫使用户重新定义一个简单的递归删除......因为这需要 15 行代码......像Files.deleteRecursively(Path)
这样的东西怎么样,或者一些可选的标志?
答案是它根本不存在于内置的 NIO.2 中。您可以使用Files.list
使用递归方法,但它是相同的,我更喜欢您拥有的解决方案。
@fgysin Kotlin 在其标准库中有这个function。真的没有理由不包括它。
@KeksArmee 除了 Kotlin 函数将始终遵循符号链接。
【参考方案1】:
您可以将 NIO 2 和 Stream API 结合起来。
Path rootPath = Paths.get("/data/to-delete");
// before you copy and paste the snippet
// - read the post till the end
// - read the javadoc to understand what the code will do
//
// a) to follow softlinks (removes the linked file too) use
// Files.walk(rootPath, FileVisitOption.FOLLOW_LINKS)
//
// b) to not follow softlinks (removes only the softlink) use
// the snippet below
try (Stream<Path> walk = Files.walk(rootPath))
walk.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.peek(System.out::println)
.forEach(File::delete);
Files.walk
- 返回rootPath
下的所有文件/目录,包括
.sorted
- 以相反的顺序对列表进行排序,因此目录本身位于包含子目录和文件之后
.map
- 将 Path
映射到 File
.peek
- 仅显示处理了哪个条目
.forEach
- 在每个 File
对象上调用 .delete()
方法
编辑 正如@Seby 首次提到,现在由@John Dough 引用,Files.walk()
应在try-with-resource
构造中使用。感谢两者。
来自Files.walkjavadoc
如果需要及时处理文件系统资源,则应使用try-with-resources构造来确保流操作完成后调用流的close方法。
编辑
这里有一些数字。
目录/data/to-delete
包含解压后的 jdk1.8.0_73 rt.jar
和最近构建的 activemq。
files: 36,427
dirs : 4,143
size : 514 MB
以毫秒为单位的时间
int. SSD ext. USB3
NIO + Stream API 1,126 11,943
FileVisitor 1,362 13,561
两个版本都在不打印文件名的情况下执行。最大的限制因素是驱动器。不是实现。
编辑
关于选项FileVisitOption.FOLLOW_LINKS
的一些附加信息。
假设以下文件和目录结构
/data/dont-delete/bar
/data/to-delete/foo
/data/to-delete/dont-delete -> ../dont-delete
使用
Files.walk(rootPath, FileVisitOption.FOLLOW_LINKS)
将遵循符号链接,并且文件 /tmp/dont_delete/bar
也将被删除。
使用
Files.walk(rootPath)
不会跟随符号链接,文件/tmp/dont_delete/bar
不会被删除。
注意:切勿在不了解其作用的情况下将代码用作复制和粘贴。
【讨论】:
@Tunaki 看看我更新的答案。我添加了一些数字。 你为什么要添加FileVisitOption.FOLLOW_LINKS
标志?这意味着您将清空符号链接指向的整个目标树。
@SubOptimal,抱歉有点粗鲁的评论,但恕我直言,以下链接在这里只是多余的。它根本不适合 topic-starter 指定的任务。
你不需要.map(Path::toFile)
; .forEach
的 arg 可以改为 Files.delete
。
@pdxleif 然后你需要在你传递给forEach(...)
的消费者函数中处理Files.delete(Path p)
的IOException
。而file.delete()
不要扔一个。【参考方案2】:
如果您已经将 Spring Core 作为项目的一部分,这里有一个简单的方法:
FileSystemUtils.deleteRecursively(dir);
来源:http://www.baeldung.com/java-delete-directory
【讨论】:
这正是我所需要的。谢谢。 像魅力一样工作!谢谢【参考方案3】:以下解决方案不需要从 Path 到 File 对象的转换:
Path rootPath = Paths.get("/data/to-delete");
final List<Path> pathsToDelete = Files.walk(rootPath).sorted(Comparator.reverseOrder()).collect(Collectors.toList());
for(Path path : pathsToDelete)
Files.deleteIfExists(path);
【讨论】:
您收集到一个列表并迭代该列表而不是仅仅用 forEach 终止流的任何特定原因? 我想我这样做是因为这样在文件删除期间更容易捕获异常。 我明白了。检查异常很糟糕。 ;-) 或者,可以将 Files.deleteIfExists() 包装在自己的方法中并在那里处理异常。问题是:在这种情况下,除了创建未经检查的异常之外,您还想做什么呢? :-) 检查的异常并不糟糕,如果它抛出了一个运行时异常,那么你甚至不会意识到这个stream
可能会中途失败并停止处理——它不只是忽略它,选中或未选中,它只是中途失败。【参考方案4】:
如果您必须仅将 Java 7 与 NIO 一起使用
Path path = Paths.get("./target/logs");
Files.walkFileTree(path, new SimpleFileVisitor<Path>()
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
Files.delete(file);
return FileVisitResult.CONTINUE;
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException
Files.delete(dir);
return FileVisitResult.CONTINUE;
);
【讨论】:
这是在虚拟文件系统上工作的唯一方法,例如 Zip:FileSystems.newFileSystem("jar:file:my.zip", ...).getPath("path")
- 只能使用 NIO 方法删除此路径。【参考方案5】:
Apache Commons IO 中的FileUtils.deleteDirectory
递归删除目录。
例子:
Path pathToBeDeleted = TEMP_DIRECTORY.resolve(DIRECTORY_NAME);
boolean result = FileUtils.deleteDirectory(pathToBeDeleted.toFile());
欲了解更多信息,请参阅Delete a Directory Recursively in Java。
【讨论】:
我明确表示“我正在寻找纯原生Java 1.8方法,所以请不要链接到外部库”...【参考方案6】:Files.walk(pathToBeDeleted).sorted(Comparator.reverseOrder()).forEach(Files::delete);
如果“需要及时处理文件系统资源”,您将需要“使用资源尝试”模式来关闭流。
另外,这可能是一个不受欢迎的评论,但使用库会更简洁、更易读。使用共享函数中的代码,它不会占用太多空间。每个查看您的代码的人都必须验证此代码是否进行了正确的删除,这绝不是显而易见的。
【讨论】:
这不会关闭流,因此目录本身可能会被阻塞并处于不一致的状态。 映射到文件的路径对我来说很糟糕。省略 .map() 并以 .forEach(Files::delete) 终止!请注意 Files 实用程序类的复数形式! @Amadán 已更新。 很遗憾,@Amadán 不正确,您不能像这样调用Files::delete
,因为它会引发检查异常,因此您的答案现在不起作用。
该死。发明检查异常的人希望已经在地狱中燃烧。这可以通过引入一个捕获异常并将其包装在 RuntimeException 中的实用方法来克服。以上是关于Java.nio:最简洁的递归目录删除的主要内容,如果未能解决你的问题,请参考以下文章
C++实现二叉树 前中后序遍历(递归与非递归)非递归实现过程最简洁版本