1. 引言

本文将介绍如何在纯Java中递归删除目录。同时也会探讨使用外部库实现目录删除的替代方案。

2. 递归删除目录

Java本身提供了删除目录的功能,但要求目录必须为空。因此对于非空目录,我们需要采用递归方式删除:

  1. 获取待删除目录的所有内容
  2. 删除所有非目录类型的子项(递归终止条件)
  3. 对当前目录的每个子目录,重复步骤1(递归步骤)
  4. 删除目录本身

以下是这个简单算法的实现:

boolean deleteDirectory(File directoryToBeDeleted) {
    File[] allContents = directoryToBeDeleted.listFiles();
    if (allContents != null) {
        for (File file : allContents) {
            deleteDirectory(file);
        }
    }
    return directoryToBeDeleted.delete();
}

可以通过以下测试用例验证该方法:

@Test
public void givenDirectory_whenDeletedWithRecursion_thenIsGone() 
  throws IOException {
 
    Path pathToBeDeleted = TEMP_DIRECTORY.resolve(DIRECTORY_NAME);

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

    assertTrue(result);
    assertFalse(
      "Directory still exists", 
      Files.exists(pathToBeDeleted));
}

测试类的@Before方法会在pathToBeDeleted位置创建包含子目录和文件的目录树,@After方法负责清理目录(如果需要)。

接下来我们看看如何使用两个最常用的库——Apache的commons-io和Spring框架的spring-core——来实现目录删除。这两个库都允许我们用一行代码完成目录删除操作。

3. 使用commons-io的FileUtils

首先需要在Maven项目中添加commons-io依赖:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.15.1</version>
</dependency>

最新版本可以在这里找到。

现在可以使用FileUtils执行各种文件操作,包括用一条语句调用deleteDirectory()

FileUtils.deleteDirectory(file);

4. 使用Spring的FileSystemUtils

或者,我们可以添加spring-core依赖到Maven项目:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>6.1.4</version>
</dependency>

最新版本可以在这里找到。

使用FileSystemUtils中的deleteRecursively()方法执行删除:

boolean result = FileSystemUtils.deleteRecursively(file);

较新的Java版本提供了执行此类IO操作的新方式,后续章节将详细介绍。

5. 使用Java 7的NIO2

Java 7引入了使用Files执行文件操作的全新方式。它允许我们遍历目录树并使用回调执行操作:

public void givenDirectory_whenDeletedWithNIO2WalkFileTree_thenIsGone() throws IOException {
    Path pathToBeDeleted = TEMP_DIRECTORY.resolve(DIRECTORY_NAME);

    Files.walkFileTree(pathToBeDeleted, 
      new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult postVisitDirectory(
          Path dir, IOException exc) throws IOException {
            Files.delete(dir);
            return FileVisitResult.CONTINUE;
        }
        
        @Override
        public FileVisitResult visitFile(
          Path file, BasicFileAttributes attrs) 
          throws IOException {
            Files.delete(file);
            return FileVisitResult.CONTINUE;
        }
    });

    assertFalse("Directory still exists", Files.exists(pathToBeDeleted));
}

Files.walkFileTree()方法遍历文件树并触发事件。我们需要为这些事件指定回调。在这个例子中,我们定义SimpleFileVisitor对生成的事件执行以下操作:

  1. ✅ 访问文件时:删除文件
  2. ⚠️ 处理目录条目前访问目录时:不做任何操作
  3. ✅ 处理完目录条目后访问目录时:删除目录(此时所有子项都已处理或删除)
  4. ❌ 无法访问文件时:重新抛出导致失败的IOException

更多关于NIO2文件操作的细节,可以参考Java NIO2文件API介绍

6. 使用Java 8的NIO2

从Java 8开始,Stream API提供了更简洁的目录删除方式:

@Test
public void givenDirectory_whenDeletedWithFilesWalk_thenIsGone() throws IOException {
    Path pathToBeDeleted = TEMP_DIRECTORY.resolve(DIRECTORY_NAME);
    try (Stream paths = Files.walk(pathToBeDeleted)) {
        paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
    }
    assertFalse("Directory still exists", Files.exists(pathToBeDeleted));
}

这里Files.walk()返回一个Path流,我们按逆序排序。这样确保目录内容路径在目录本身之前被处理。然后我们将Path映射为File并删除每个File

7. 总结

这篇快速教程探讨了删除目录的不同方法。我们不仅看到了如何使用递归删除,还研究了各种库、基于事件的NIO2以及采用函数式编程范式的Java 8 Path Stream。

本文的所有源代码和测试用例都可以在GitHub上获取。


原始标题:Delete a Directory Recursively in Java | Baeldung