跳到主要内容

Kotlin 文件操作

文件操作是程序开发中的常见需求。Kotlin 通过扩展函数简化了 Java I/O 类的使用,让文件读写变得更加简洁。本章将介绍 Kotlin 中常用的文件操作方法。

读取文件

读取整个文件

使用 readText() 方法一次性读取文件全部内容:

import java.io.File

fun main() {
val file = File("test.txt")

// 读取全部内容为字符串
val content = file.readText()
println(content)

// 读取为字节
val bytes = file.readBytes()
println("文件大小: ${bytes.size} 字节")

// 指定编码
val contentUtf8 = file.readText(Charsets.UTF_8)
}

适用场景:适合读取小文件,大文件可能导致内存问题。

按行读取

使用 readLines()forEachLine() 逐行处理:

import java.io.File

fun main() {
val file = File("test.txt")

// 方式一:readLines() 返回列表
val lines = file.readLines()
lines.forEach { println(it) }

// 方式二:forEachLine() 逐行处理(推荐大文件)
file.forEachLine { line ->
println("行内容: $line")
}

// 方式三:使用 useLines 自动关闭流
file.useLines { lines ->
lines.filter { it.contains("Kotlin") }
.forEach { println(it) }
}
}

useLines 的优势:使用完毕后自动关闭文件流,适合处理大文件。

使用缓冲流

对于大文件,使用缓冲流提高性能:

import java.io.File

fun main() {
val file = File("large.txt")

// 使用 BufferedReader
file.bufferedReader().use { reader ->
var line: String?
while (reader.readLine().also { line = it } != null) {
println(line)
}
}

// 简化写法
file.bufferedReader().forEachLine { println(it) }
}

写入文件

写入文本

import java.io.File

fun main() {
val file = File("output.txt")

// 写入文本(覆盖原内容)
file.writeText("第一行内容")

// 追加文本
file.appendText("\n第二行内容")

// 写入多行
file.writeLines(listOf(
"第一行",
"第二行",
"第三行"
))

// 写入字节数组
file.writeBytes(byteArrayOf(72, 101, 108, 108, 111))
}

使用缓冲流写入

import java.io.File

fun main() {
val file = File("output.txt")

// 使用 BufferedWriter
file.bufferedWriter().use { writer ->
writer.write("第一行")
writer.newLine()
writer.write("第二行")
writer.newLine()
writer.write("第三行")
}

// 追加模式
file.bufferedWriter(Charsets.UTF_8, true).use { writer ->
writer.appendLine("追加的内容")
}
}

实用示例:日志文件

import java.io.File
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

class Logger(private val file: File) {
private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")

fun log(message: String) {
val timestamp = LocalDateTime.now().format(formatter)
file.appendText("[$timestamp] $message\n")
}

fun error(message: String) {
log("[ERROR] $message")
}

fun info(message: String) {
log("[INFO] $message")
}
}

fun main() {
val logger = Logger(File("app.log"))
logger.info("应用启动")
logger.error("发生错误")
}

文件信息

检查文件属性

import java.io.File

fun main() {
val file = File("test.txt")

// 存在性检查
println("是否存在: ${file.exists()}")

// 类型检查
println("是文件: ${file.isFile}")
println("是目录: ${file.isDirectory}")

// 基本信息
println("文件名: ${file.name}")
println("完整路径: ${file.absolutePath}")
println("父目录: ${file.parent}")
println("文件大小: ${file.length()} 字节")

// 权限检查
println("可读: ${file.canRead()}")
println("可写: ${file.canWrite()}")
println("可执行: ${file.canExecute()}")

// 修改时间
val lastModified = java.util.Date(file.lastModified())
println("最后修改: $lastModified")

// 扩展名
println("扩展名: ${file.extension}")
println("无扩展名: ${file.nameWithoutExtension}")
}

文件比较

import java.io.File

fun main() {
val file1 = File("test.txt")
val file2 = File("test.txt")

// 比较路径
println(file1 == file2) // false(不同 File 对象)

// 比较实际文件
println(file1.canonicalPath == file2.canonicalPath) // true

// 复制文件
file1.copyTo(File("test_copy.txt"), overwrite = true)

// 移动/重命名
file1.renameTo(File("renamed.txt"))
}

目录操作

创建目录

import java.io.File

fun main() {
// 创建单层目录
val dir = File("mydir")
dir.mkdir()

// 创建多层目录
val nestedDir = File("parent/child/grandchild")
nestedDir.mkdirs()

// 创建临时目录
val tempDir = createTempDir("myapp_")
println("临时目录: ${tempDir.absolutePath}")
}

遍历目录

import java.io.File

fun main() {
val dir = File(".")

// 列出文件名
val files = dir.list()
files?.forEach { println(it) }

// 列出 File 对象
val fileObjects = dir.listFiles()
fileObjects?.forEach {
println("${it.name} - ${if (it.isDirectory) "目录" else "文件"}")
}

// 过滤文件
val kotlinFiles = dir.listFiles { file ->
file.extension == "kt"
}

// 递归遍历(深度优先)
dir.walkTopDown()
.filter { it.isFile }
.forEach { println(it.path) }

// 递归遍历(广度优先)
dir.walkBottomUp()
.filter { it.extension == "kt" }
.forEach { println(it.path) }
}

删除目录

import java.io.File

fun main() {
val dir = File("mydir")

// 删除空目录
dir.delete()

// 递归删除(包含内容)
dir.deleteRecursively()

// 安全删除
if (dir.exists()) {
dir.deleteRecursively()
}
}

文件复制与移动

复制文件

import java.io.File

fun main() {
val source = File("source.txt")
val dest = File("dest.txt")

// 方式一:使用 copyTo
source.copyTo(dest, overwrite = true)

// 方式二:使用输入输出流
source.inputStream().use { input ->
dest.outputStream().use { output ->
input.copyTo(output)
}
}

// 复制到目录
val targetDir = File("backup")
targetDir.mkdir()
source.copyTo(File(targetDir, source.name), overwrite = true)
}

移动文件

import java.io.File

fun main() {
val source = File("source.txt")
val dest = File("moved.txt")

// 移动/重命名
source.renameTo(dest)

// 跨目录移动
val targetDir = File("archive")
targetDir.mkdir()
source.renameTo(File(targetDir, source.name))
}

使用 Path API

Kotlin 可以使用 Java NIO 的 Path API 进行更现代的文件操作:

import java.nio.file.*
import java.nio.file.attribute.*

fun main() {
val path = Paths.get("test.txt")

// 读取文件
val content = Files.readString(path)
println(content)

// 写入文件
Files.writeString(path, "新内容", StandardOpenOption.CREATE)

// 追加内容
Files.writeString(path, "\n追加内容", StandardOpenOption.APPEND)

// 检查文件属性
println("大小: ${Files.size(path)}")
println("最后修改: ${Files.getLastModifiedTime(path)}")
println("是否隐藏: ${Files.isHidden(path)}")

// 创建文件
val newFile = Files.createFile(Paths.get("newfile.txt"))

// 创建目录
Files.createDirectory(Paths.get("newdir"))
Files.createDirectories(Paths.get("a/b/c"))

// 删除文件
Files.deleteIfExists(path)

// 复制文件
Files.copy(Paths.get("source.txt"), Paths.get("dest.txt"))

// 移动文件
Files.move(Paths.get("old.txt"), Paths.get("new.txt"))

// 遍历目录
Files.list(Paths.get(".")).use { stream ->
stream.forEach { println(it) }
}
}

文件监听

使用 WatchService 监听文件变化:

import java.nio.file.*

fun main() {
val watchService = FileSystems.getDefault().newWatchService()
val dir = Paths.get(".")

dir.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY
)

println("开始监听目录: ${dir.toAbsolutePath()}")

while (true) {
val key = watchService.take()

for (event in key.pollEvents()) {
val filename = event.context() as Path
println("${event.kind()}: $filename")
}

if (!key.reset()) break
}
}

最佳实践

1. 使用 use 自动关闭资源

// 推荐:使用 use
File("test.txt").bufferedReader().use { reader ->
// 自动关闭
}

// 不推荐:手动管理
val reader = File("test.txt").bufferedReader()
try {
// ...
} finally {
reader.close()
}

2. 处理异常

import java.io.File
import java.io.IOException

fun readFile(path: String): String? {
return try {
File(path).readText()
} catch (e: IOException) {
println("读取失败: ${e.message}")
null
}
}

3. 检查文件存在

import java.io.File

fun safeRead(file: File): String? {
if (!file.exists()) {
println("文件不存在")
return null
}
if (!file.canRead()) {
println("文件不可读")
return null
}
return file.readText()
}

小结

本章我们学习了 Kotlin 文件操作的核心内容:

  1. 读取文件readText()readLines()forEachLine()
  2. 写入文件writeText()appendText()writeLines()
  3. 文件信息:检查存在性、类型、权限、属性
  4. 目录操作:创建、遍历、删除
  5. 复制移动copyTo()renameTo()
  6. Path API:Java NIO 现代文件操作
  7. 最佳实践:资源管理、异常处理

Kotlin 的文件操作扩展函数让 I/O 处理变得简洁高效,同时保持与 Java 的完全兼容。

练习

  1. 编写程序统计一个文本文件的行数、单词数和字符数
  2. 实现一个简单的配置文件解析器
  3. 递归遍历目录,找出所有扩展名为 .kt 的文件
  4. 实现文件备份功能,将文件复制到带时间戳的目录
  5. 使用 Path API 实现文件搜索功能