Java.nio:最简洁的递归目录删除

Java.nio: most concise recursive directory delete

我目前正在尝试递归删除目录...奇怪的是,我能找到的最短代码是以下结构,采用了 ad-hoc 内部 class 并且在 访问者模式中 ...

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 方法,所以请不要 link 到外部库...

您可以结合使用 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 - return rootPath 以下的所有 files/directories 包括
  • .sorted - 以相反的顺序对列表进行排序,因此目录本身位于包含子目录和文件之后
  • .map - 将 Path 映射到 File
  • .peek - 是否仅显示处理了哪个条目
  • .forEach - 在每个 File 对象上调用 .delete() 方法

编辑 正如@Seby and now cited by @John Dough首先提到的,Files.walk()应该用在try-with-resource结构中。感谢两位。

来自 Files.walk javadoc

If timely disposal of file system resources is required, the try-with-resources construct should be used to ensure that the stream's close method is invoked after the stream operations are completed.

编辑

这是一些数字。
目录 /data/to-delete 包含解压缩的 rt.jar of jdk1.8.0_73 和最近构建的 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 不会被删除。

注意:切勿在不了解其作用的情况下将代码用作复制和粘贴。

以下解决方案不需要从 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);
}

如果你必须只使用 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;
  }
});

如果您已经将 Spring 核心作为项目的一部分,这里有一个简单的方法:

FileSystemUtils.deleteRecursively(dir);

来源:http://www.baeldung.com/java-delete-directory

Files.walk(pathToBeDeleted).sorted(Comparator.reverseOrder()).forEach(Files::delete);

如果 "timely disposal of file system resources is required",您将需要 "try with resources" 模式来关闭流。

此外,这可能是一个不受欢迎的评论,但使用库会更清晰、更易读。共享函数中的代码,不会占用太多space。每个查看您的代码的人都必须验证此代码是否进行了正确的删除,这绝不是显而易见的。

FileUtils.deleteDirectory from Apache Commons IO 递归删除目录。

示例:

Path pathToBeDeleted = TEMP_DIRECTORY.resolve(DIRECTORY_NAME);

boolean result = FileUtils.deleteDirectory(pathToBeDeleted.toFile());

有关详细信息,请参阅 Delete a Directory Recursively in Java