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 8 Files.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:最简洁的递归目录删除的主要内容,如果未能解决你的问题,请参考以下文章

Java NIO FileVisitor 高效删除文件

C++实现二叉树 前中后序遍历(递归与非递归)非递归实现过程最简洁版本

全网最简洁全排列源代码(递归)

jdk 1.7系列处理目录和目录树

你的Win10右键菜单还有一大堆用不上的选项?看完还你最简洁的右键菜单!!!

Java 8:递归复制目录?