Go 高手(22):文件系统——io/fs、embed、path/filepath
更新时间:2026-09-01。本文是
languages/go/intermediate/高手层第 22 篇,接 JSON 进阶。Go 1.16 引入了io/fs包,提供了统一的文件系统接口。os.DirFS读取目录,embed.FS嵌入静态文件,testing/fstest做测试。理解这些接口,文件操作更灵活。
本文要回答的问题
io/fs.FS接口是什么?为什么要有统一的文件系统接口?embed.FS怎么在编译时嵌入静态文件?适用场景是什么?path/filepath的常用操作有哪些?路径分隔符自动处理?- 怎么遍历目录里所有文件?只遍历一层和递归遍历分别怎么做?
一、io/fs.FS 接口
Go 1.16 引入 io/fs 包,核心是 fs.FS 接口:
go
type FS interface {
Open(name string) (File, error)
}任何实现了 Open 方法的类型,都可以作为文件系统。这让你可以:
- 用
os.DirFS读取真实文件系统 - 用
embed.FS读取嵌入的静态文件 - 用
fstest.MapFS在测试中模拟文件系统 - 写一个
zip.FS读取 zip 包里的文件
go
// 三种文件系统用法一致
var fsys fs.FS
// 1. 真实目录
fsys = os.DirFS("/path/to/dir")
// 2. 嵌入文件
//go:embed static/*
var staticFS embed.FS
fsys = staticFS
// 3. 测试文件系统
fsys = fstest.MapFS{
"hello.txt": {Data: []byte("hello")},
}二、embed:编译时嵌入静态文件
go
import "embed"
// 嵌入单个文件
//go:embed config.json
var configContent string
// 嵌入多个文件
//go:embed static/*
//go:embed templates/*
var staticFS embed.FS
// 嵌入到 io/fs.FS 接口
//go:embed assets/*
var assetsFS embed.FSembed 的限制:
- 文件必须在源文件所在目录或子目录(不能嵌入绝对路径或
../路径) - 嵌入的是只读文件系统,运行时不能修改
- 在
init函数之前就加载,编译时直接嵌入到二进制
用途: 把静态文件编译进二进制,部署时不需要额外带文件。适合 HTML 模板、前端构建产物、配置文件。
go
// 使用嵌入的模板
func handler(w http.ResponseWriter, r *http.Request) {
tmpl, _ := template.ParseFS(templatesFS, "templates/index.html")
tmpl.Execute(w, data)
}三、path/filepath:路径操作
go
import "path/filepath"
// 路径拼接(自动处理分隔符)
path := filepath.Join("dir", "sub", "file.txt") // dir/sub/file.txt
// 获取目录和文件名
dir := filepath.Dir("/path/to/file.txt") // /path/to
base := filepath.Base("/path/to/file.txt") // file.txt
// 获取扩展名
ext := filepath.Ext("file.txt") // .txt
// 相对路径
rel, _ := filepath.Rel("/a/b", "/a/b/c/d") // c/d
// 匹配路径模式
matched, _ := filepath.Match("*.txt", "file.txt") // true
// 遍历目录
filepath.WalkDir("/path", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Println(path, d.IsDir())
return nil
})filepath.Join 是最常用的,自动处理路径分隔符,不用手动拼 / 或 \。
四、fs.DirEntry:目录项
go
// 只读一层目录
entries, err := os.ReadDir("/path")
for _, entry := range entries {
fmt.Println(entry.Name()) // 文件名
fmt.Println(entry.IsDir()) // 是否目录
info, _ := entry.Info() // 详细信息
fmt.Println(info.Size()) // 文件大小
fmt.Println(info.ModTime()) // 修改时间
fmt.Println(info.Mode()) // 权限
}os.ReadDir 只读一层,不递归。递归用 filepath.WalkDir。
五、读写文件(进阶层常用)
go
// 读取整个文件
data, err := os.ReadFile("file.txt")
// 写入整个文件(覆盖)
os.WriteFile("file.txt", data, 0644)
// 追加写入
f, _ := os.OpenFile("file.txt", os.O_APPEND|os.O_WRONLY, 0644)
defer f.Close()
f.WriteString("new line\n")六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
embed 路径包含 ../ | 编译错误 | embed 只能嵌入同目录和子目录 |
| embed 文件修改后要重新编译 | 运行时还是旧文件 | 修改文件后必须重新 go build |
用 ioutil.ReadDir(已弃用) | 编译警告 | 用 os.ReadDir |
路径拼接手动拼 / | Windows 上路径错误 | 用 filepath.Join |
忘记 filepath.WalkDir 的 err 检查 | 遍历中途出错,不处理 | 检查 err,决定是否跳过或返回 |
相关与延伸
下一篇:日志最佳实践——log/slog、结构化日志、日志级别;io/fs 的底层是 io.Reader,见 io 包与接口设计。
一句话总结
Go 文件系统:io/fs.FS 接口统一了文件系统抽象,os.DirFS 读真实目录,embed.FS 编译时嵌入静态文件,fstest.MapFS 测试用;filepath.Join 自动处理路径分隔符,os.ReadDir 读一层目录,filepath.WalkDir 递归遍历;embed 把文件编译进二进制,部署不需要额外带文件。