跳到主要内容

File(文件)

Go 的文件操作由 os 提供文件句柄,并通过 iobufio 等包处理流。文件句柄是有限资源:成功打开后必须关闭,写入场景还要考虑 CloseSync 返回的错误。

小文件读写

文件大小受控时可使用 os.ReadFileos.WriteFile

package main

import (
"fmt"
"os"
)

func main() {
path := "notes.txt"
if err := os.WriteFile(path, []byte("hello\n"), 0o600); err != nil {
panic(fmt.Errorf("write %s: %w", path, err))
}

data, err := os.ReadFile(path)
if err != nil {
panic(fmt.Errorf("read %s: %w", path, err))
}
fmt.Print(string(data))
}

ReadFile 会把全部内容放进内存,因此只适合大小已受控的文件。权限受操作系统和 umask 影响;敏感内容应使用最小权限,且不要把内容写入日志。

流式读取与关闭错误

大文件应流式处理。下面的函数完整返回打开、扫描和关闭错误:

func countLines(path string) (count int, err error) {
file, err := os.Open(path)
if err != nil {
return 0, fmt.Errorf("open %s: %w", path, err)
}
defer func() {
if closeErr := file.Close(); err == nil && closeErr != nil {
err = fmt.Errorf("close %s: %w", path, closeErr)
}
}()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
count++
}
if err := scanner.Err(); err != nil {
return 0, fmt.Errorf("scan %s: %w", path, err)
}
return count, nil
}

此片段需要导入 bufiofmtScanner 有默认 token 大小限制,处理长行时应调整 Buffer 或改用 bufio.Reader。写入已有句柄时还要检查 Write 返回的字节数和错误;需要持久化保证时调用 Sync 并处理其错误。

路径与替换

使用 filepath.Join 拼接路径,不要直接拼接分隔符。不要依赖当前工作目录:命令行、测试和服务部署的工作目录可能不同。更新重要文件时,通常先在同一目录写入临时文件、Sync、关闭后再重命名;这能缩小中断造成半写入文件的窗口,但具体原子性仍受文件系统语义约束。

常见错误

  • Open 成功后忘记关闭文件。
  • 一次性读取不受控的大文件,造成内存峰值。
  • 忽略 Scanner.Err、部分写入、CloseSync 的错误。
  • 在长循环中累计 defer,使资源迟迟不能释放。
  • 依赖当前工作目录,或把不可信路径直接用于文件操作。

Node.js 的 fs.readFileos.ReadFile 用途相似,但 Go 的句柄生命周期和错误返回更显式。文件 I/O 不能因使用 goroutine 而自动获得取消或资源管理;调用方仍需定义退出策略。

小结

  • 小文件使用 os.ReadFile/os.WriteFile,大文件使用流式 API。
  • defer 关闭句柄,并在需要时把关闭错误并入返回值。
  • 文件大小、路径、权限、工作目录和持久化语义都是工程边界。