1. 概述

从 Kotlin 1.5 开始,标准库中引入了对 java.nio.file.Path 的一系列扩展函数和操作符。本文将通过实际示例,带你掌握这些 API 的使用方式。

完整的扩展函数列表可查阅官方文档:kotlin.io.path 扩展函数

这些扩展使得操作文件系统更加符合 Kotlin 风格,推荐优先使用 Path 而非老旧的 File 类。关于两者的对比,可参考文章:Java – Path vs File

经验之谈:早年用 File 踩过不少坑,比如路径拼接跨平台问题、异常处理不统一等。现在有了 Path + Kotlin 扩展,代码清晰又安全。


2. 使用 Path 操作文件系统

2.1 文件与目录的基本操作

Path 是 Java NIO 中表示文件或目录路径的核心类。借助 kotlin.io.path 包中的扩展函数,我们可以非常简洁地完成常见操作。

列出目录内容

Path("~/Downloads").listDirectoryEntries()

该方法返回一个 List<Path>,包含目标目录下所有子项(文件和子目录)。

复制文件

Path("source.txt").copyTo(Path("destination.txt"))

移动文件(剪切)

Path("source.txt").moveTo(Path("destination.txt"))

⚠️ 注意:默认情况下,moveTo 不会覆盖已存在的目标文件,若需覆盖,传参即可:

Path("source.txt").moveTo(Path("dest.txt"), overwrite = true)

扩展函数背后的原理

moveTo 为例,其内部实现如下:

public inline fun Path.moveTo(target: Path, overwrite: Boolean = false): Path {
    val options = if (overwrite) arrayOf<CopyOption>(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
    return Files.move(this, target, *options)
}

亮点解析

  • 借助 Kotlin 的默认参数,简化了调用逻辑
  • 避免手动构造 StandardCopyOption.REPLACE_EXISTING 数组,提升可读性

相比原生 Java 写法,这简直是降维打击。


2.2 访问嵌套路径

Kotlin 提供了 / 操作符(即 div() 函数),让路径拼接像写 URI 一样自然。

举个实用场景:实现“软删除”功能,把文件移入 .deleted 回收目录:

fun softDelete(path: String) {
    val fileToDelete = Path(path)

    val destinationDirectory = fileToDelete.parent / ".deleted"
    if (destinationDirectory.notExists()) {
        destinationDirectory.createDirectory()
    }

    fileToDelete.moveTo(destinationDirectory / fileToDelete.name)
}

📌 关键点说明:

  • fileToDelete.parent 获取父目录
  • / ".deleted" 使用 / 拼接新路径
  • notExists()exists() 的反向判断扩展
  • createDirectory() 自动创建目录(支持多级)

⚠️ 踩坑提醒:别忘了检查目标目录是否存在,否则 moveTo 可能抛出异常。


3. 文件内容读写操作

3.1 写入文本文件

一次性写入字符串

Path("destination.txt").writeText("<file content>")

默认行为是覆盖已有文件。可通过传参控制行为:

Path("destination.txt").writeText(
    text = "<file content>",
    charset = Charsets.UTF_8,
    StandardOpenOption.CREATE_NEW
)

StandardOpenOption.CREATE_NEW 表示仅当文件不存在时才创建,否则抛出 FileAlreadyExistsException

分行写入(适合大文件)

fun writeFileLineByLine(path: String) {
    Path(path).bufferedWriter().use { writer ->
        (1..10).forEach { i ->
            writer.appendLine("Line #$i")
        }
    }
}

📌 推荐模式:

  • 使用 bufferedWriter() 获取带缓冲的写入器
  • use 确保资源自动关闭(RAII 风格)
  • 适合逐行生成日志、CSV 等场景

3.2 读取文本文件

一次性读取全部内容为字符串

val content: String = Path("source.txt").readText()

读取为行列表

val lines: List<String> = Path("source.txt").readLines()

⚠️ 注意:此方法会将整个文件加载进内存,不适合大文件

流式逐行处理(推荐用于大文件)

fun countLines(path: String): Int {
    var lines = 0
    Path(path).useLines {
        it.forEach { _ -> // 我们不需要具体行内容
            lines++
        }
    }
    return lines
}

useLines() 特点:

  • 内部使用 BufferedReader,逐行读取
  • 自动管理资源释放
  • 适用于统计、过滤、转换等流式处理场景

💡 小技巧:配合 Sequence 可实现懒加载处理,进一步优化性能。


4. 递归处理文件

kotlin.io.path 还提供了一些强大的递归操作函数,但请注意:

当前仍是实验性 API(Kotlin 1.8),未来可能变更

主要函数包括:

如何启用实验性 API?

有两种方式:

✅ 方式一:注解标记

@OptIn(ExperimentalPathApi::class)
fun walkDirectory() {
    Path("my-folder").walk().forEach { println(it) }
}

✅ 方式二:编译器参数(推荐) 在 build.gradle.kts 中添加:

kotlinOptions {
    freeCompilerArgs += "-opt-in=kotlin.io.path.ExperimentalPathApi"
}

📌 替代方案: 如果不想用实验性 API,可以退回到稳定的 File 扩展,详见:Kotlin 递归列出文件


5. 总结

本文介绍了 Kotlin 1.5+ 对 java.nio.file.Path 的扩展能力,核心要点如下:

优势总结

  • 自 Kotlin 1.5 起,Path 扩展已稳定可用
  • 提供了 idiomatic 的文件操作方式,如 / 拼接路径、writeText/readText
  • 充分利用 Kotlin 语言特性(默认参数、作用域函数、操作符重载)简化代码
  • 推荐替代旧版 File 操作,更安全、更现代

注意事项

  • 递归相关 API 仍处于实验阶段,谨慎用于生产
  • 注意资源管理,优先使用 useuseLines 等自动释放机制

所有示例代码已上传至 GitHub:Baeldung Kotlin 教程仓库


原始标题:Kotlin Path API