1. 概述

处理文件输入是每个开发者的日常操作。虽然 Java 已经提供了多种操作文件的方式,但 Kotlin 在此基础上进一步封装,让遍历目录树变得更简洁高效。

本文将介绍如何使用 Kotlin 标准库中的方法,递归列出目录下的所有文件。

2. 递归列出文件

Kotlin 为 File 类扩展了三个用于遍历文件树的方法:walk()walkTopDown()walkBottomUp()

这些方法底层都依赖于一个核心类 —— FileTreeWalk,我们先来深入理解它。

2.1. FileTreeWalk 类解析

无论调用上述哪个方法,最终都会创建一个 FileTreeWalk 实例来执行实际的遍历逻辑。

这个类有两个关键特性需要关注:

私有构造函数
FileTreeWalk 的构造函数是私有的,因此无法直接实例化,只能通过 walk() 等扩展方法间接使用。

继承自 Sequence
它实现了 Sequence<File> 接口,这意味着我们可以像操作集合一样使用 Kotlin 提供的各种函数式操作,比如 mapfilterforEach 等,而且是惰性求值,不会一次性加载整个目录结构,节省内存。

此外,FileTreeWalk 支持两种遍历顺序:

  • TOP_DOWN(默认):先访问目录本身,再递归进入子文件和子目录。
  • BOTTOM_UP:先深度遍历到底层文件和子目录,最后才访问父目录。

两者都是基于深度优先搜索(DFS),区别仅在于目录的访问时机。

2.2. walk 方法详解

walk() 是 Kotlin 提供的核心遍历方法,定义如下:

public fun File.walk(direction: FileWalkDirection = FileWalkDirection.TOP_DOWN): FileTreeWalk 
  = FileTreeWalk(this, direction)

⚠️ 注意:

  • 这是一个对 Java File 类的扩展函数。
  • 默认行为是 TOP_DOWN 遍历。

假设我们有以下目录结构:

src/test/resources
├── one-in
│   ├── empty-folder
│   ├── one-in-file.md
│   └── two-in
│       ├── two-in-1.md
│       └── two-in-2.md
└── root-file.md

使用默认 TOP_DOWN 模式遍历:

File("src/test/resources").walk().forEach {
    println(it)
}

输出结果为:

src/test/resources
src/test/resources/root-file.md
src/test/resources/one-in
src/test/resources/one-in/one-in-file.md
src/test/resources/one-in/empty-folder
src/test/resources/one-in/two-in
src/test/resources/one-in/two-in/two-in-2.md
src/test/resources/one-in/two-in/two-in-1.md

可以看到,目录在对应文件之前被访问。

如果我们改为 BOTTOM_UP 模式:

File("src/test/resources").walk(FileWalkDirection.BOTTOM_UP).forEach {
    println(it)
}

输出变为:

src/test/resources/root-file.md
src/test/resources/one-in/one-in-file.md
src/test/resources/one-in/empty-folder
src/test/resources/one-in/two-in/two-in-2.md
src/test/resources/one-in/two-in/two-in-1.md
src/test/resources/one-in/two-in
src/test/resources/one-in
src/test/resources

此时,所有文件和子目录都被优先访问,父目录最后才出现。

💡 踩坑提醒:直接传 FileWalkDirection 枚举显得冗长,影响代码可读性。好在 Kotlin 提供了更优雅的替代方案。

2.3. 更简洁的写法:walkTopDown 与 walkBottomUp

为了提升可读性,Kotlin 提供了两个封装好的快捷方法:

// 等价于 walk(FileWalkDirection.TOP_DOWN)
File("src/test/resources").walkTopDown().forEach {
    println(it)
}

// 等价于 walk(FileWalkDirection.BOTTOM_UP)
File("src/test/resources").walkBottomUp().forEach {
    println(it)
}

✅ 优势:

  • 语义清晰,无需显式传参。
  • 更符合 Kotlin 的惯用写法(idiomatic Kotlin)。

这两个方法本质上只是 walk() 的语法糖,但在实际项目中推荐优先使用它们,提高代码可维护性。

3. 总结

Kotlin 通过 walk() 及其变体,极大地简化了文件树的递归遍历操作。结合 Sequence 的惰性特性,既能高效处理大目录,又能灵活配合函数式编程风格。

文章中的示例代码已上传至 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-files


原始标题:Listing Files Recursively in Kotlin