1. 引言
本文将介绍如何在纯Java中递归删除目录。同时也会探讨使用外部库实现目录删除的替代方案。
2. 递归删除目录
Java本身提供了删除目录的功能,但要求目录必须为空。因此对于非空目录,我们需要采用递归方式删除:
- 获取待删除目录的所有内容
- 删除所有非目录类型的子项(递归终止条件)
- 对当前目录的每个子目录,重复步骤1(递归步骤)
- 删除目录本身
以下是这个简单算法的实现:
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
对生成的事件执行以下操作:
- ✅ 访问文件时:删除文件
- ⚠️ 处理目录条目前访问目录时:不做任何操作
- ✅ 处理完目录条目后访问目录时:删除目录(此时所有子项都已处理或删除)
- ❌ 无法访问文件时:重新抛出导致失败的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上获取。