Go 遍历获取文件夹下所有文件路径的 3 种方法
在日常开发中,我们经常会遇到需要扫描某个目录、拿到里面所有文件路径的场景。比如批量处理图片、遍历日志目录、构建静态资源索引等。Go 语言标准库提供了非常方便的工具来完成这类任务,这篇文章会带你逐一了解 3 种常见的实现方式,并分析它们各自的适用场景。
方法一:filepath.Walk(经典方案)
filepath.Walk 是 Go 标准库中最经典的目录遍历函数,从 Go 1.0 就存在了。它会自动递归地遍历指定目录下的所有子目录和文件,并对每个条目执行你传入的回调函数。
package main
import (
"fmt"
"os"
"path/filepath"
)
// GetAllFiles 使用 filepath.Walk 遍历目录下所有文件
func GetAllFiles(dir string) ([]string, error) {
var files []string
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return nil
})
return files, err
}
func main() {
files, err := GetAllFiles("./testdata")
if err != nil {
fmt.Println("遍历出错:", err)
return
}
for _, f := range files {
fmt.Println(f)
}
}要点说明:
filepath.Walk接收两个参数:目录路径和一个WalkFunc回调函数。- 回调函数中的
err参数需要优先判断。如果目录不存在或权限不足,err不为nil,此时info可能是nil,直接访问info.IsDir()会导致 panic。 - 它会对遍历到的每个文件和目录都调用
os.Lstat,在文件数量很大时会带来一定的性能开销。
方法二:filepath.WalkDir(Go 1.16+ 推荐)
Go 1.16 新增了 filepath.WalkDir,它是 filepath.Walk 的改进版。最大的区别在于回调函数接收的是 fs.DirEntry 而非 os.FileInfo,这意味着它不需要对每个文件都调用 os.Lstat,在大目录下遍历速度明显更快。
package main
import (
"fmt"
"io/fs"
"path/filepath"
)
// GetAllFilesV2 使用 filepath.WalkDir 遍历目录下所有文件(推荐)
func GetAllFilesV2(dir string) ([]string, error) {
var files []string
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
files = append(files, path)
}
return nil
})
return files, err
}
func main() {
files, err := GetAllFilesV2("./testdata")
if err != nil {
fmt.Println("遍历出错:", err)
return
}
for _, f := range files {
fmt.Println(f)
}
}要点说明:
- 如果你的项目使用 Go 1.16 及以上版本,优先选择
filepath.WalkDir。 - 在包含上万个文件的目录中实测,
WalkDir比Walk快 20%~50% 左右,主要是省去了逐个文件调用Lstat的开销。 - 如果你确实需要文件大小、修改时间等详细信息,可以在回调里手动调用
d.Info()来获取。
方法三:os.ReadDir + 递归
如果你需要更灵活的控制(比如只遍历特定层级、跳过某些目录),可以用 os.ReadDir 手动递归实现。os.ReadDir 同样是 Go 1.16 引入的,用来替代已废弃的 ioutil.ReadDir。
package main
import (
"fmt"
"os"
"path/filepath"
)
// GetAllFilesRecursive 使用 os.ReadDir 手动递归遍历
func GetAllFilesRecursive(dir string) ([]string, error) {
var files []string
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
for _, entry := range entries {
fullPath := filepath.Join(dir, entry.Name())
if entry.IsDir() {
subFiles, err := GetAllFilesRecursive(fullPath)
if err != nil {
return nil, err
}
files = append(files, subFiles...)
} else {
files = append(files, fullPath)
}
}
return files, nil
}
func main() {
files, err := GetAllFilesRecursive("./testdata")
if err != nil {
fmt.Println("遍历出错:", err)
return
}
for _, f := range files {
fmt.Println(f)
}
}要点说明:
- 手动递归的好处是你可以在遍历过程中加入自定义逻辑,比如跳过
.git目录、只扫描指定后缀的文件等。 - 注意不要使用已废弃的
ioutil.ReadDir,Go 1.16 之后应该统一使用os.ReadDir。
3 种方法对比
| 对比项 | filepath.Walk | filepath.WalkDir | os.ReadDir + 递归 |
|---|---|---|---|
| 最低版本要求 | Go 1.0 | Go 1.16 | Go 1.16 |
| 性能 | 一般(逐文件 Lstat) | 较好(延迟获取 FileInfo) | 较好 |
| 灵活性 | 中等 | 中等 | 高(可自定义遍历逻辑) |
| 代码复杂度 | 低 | 低 | 中等 |
| 推荐程度 | 旧项目兼容 | 日常首选 | 需要自定义控制时使用 |
简单总结一下:大多数场景直接用 filepath.WalkDir 就够了,既简洁又高效。如果你需要跳过特定目录、按条件过滤文件,手动递归会更灵活。filepath.Walk 则适合需要兼容 Go 1.16 以前版本的项目。
实用技巧:按后缀过滤文件
实际项目中,我们往往不需要拿到所有文件,而是只关心某种类型的文件。下面演示如何在遍历时按后缀过滤:
// GetFilesByExt 获取指定后缀的文件列表
func GetFilesByExt(dir string, ext string) ([]string, error) {
var files []string
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() && filepath.Ext(path) == ext {
files = append(files, path)
}
return nil
})
return files, err
}
// 使用示例:只获取 .go 文件
// goFiles, err := GetFilesByExt("./src", ".go")这个小函数在批量处理日志文件、扫描配置文件等场景中非常实用。
常见问题
1. filepath.Walk 和 filepath.WalkDir 有什么区别?
最核心的区别是性能。filepath.Walk 会对每个文件调用 os.Lstat 来获取 os.FileInfo,而 filepath.WalkDir 使用 fs.DirEntry,只在你主动调用 d.Info() 时才会去读取文件元信息。在大目录下,这个差异会非常明显。
2. ioutil.ReadDir 还能用吗?
从 Go 1.16 开始,ioutil.ReadDir 已被标记为废弃(deprecated),官方建议改用 os.ReadDir。两者功能几乎一样,但 os.ReadDir 返回的是 []fs.DirEntry 而非 []os.FileInfo,性能更好一些。
3. 遍历时遇到权限不足的文件怎么办?
在回调函数中,err 参数会携带权限相关的错误信息。你可以选择跳过出错的文件继续遍历,只需要返回 nil 而不是返回 err 就行:
filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
fmt.Printf("跳过无权限的路径: %s\n", path)
return nil // 返回 nil 继续遍历,返回 err 则终止
}
// 正常处理逻辑
return nil
})4. 遍历时如何跳过某些目录?
在回调中返回 filepath.SkipDir 即可跳过当前目录:
filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// 跳过 .git 和 node_modules 目录
if d.IsDir() && (d.Name() == ".git" || d.Name() == "node_modules") {
return filepath.SkipDir
}
if !d.IsDir() {
fmt.Println(path)
}
return nil
})总结
Go 语言中遍历文件夹获取所有文件路径,常用的方式有 3 种:
- filepath.Walk — 兼容性最好,适合老版本 Go 项目。
- filepath.WalkDir — Go 1.16+ 推荐方案,性能优于 Walk,日常开发首选。
- os.ReadDir + 递归 — 灵活性最高,适合需要自定义遍历逻辑的场景。
对于绝大多数开发者来说,直接使用 filepath.WalkDir 就能满足需求。如果你的项目还在用 Go 1.16 之前的版本,那就用 filepath.Walk 也完全没问题。
如果大家对 Go 文件遍历还有什么疑问,或者在实际项目中遇到了其他文件操作相关的问题,欢迎在评论区留言交流~~~
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/go-walk-dir-get-all-file-paths/
备用原文链接: https://blog.fiveyoboy.com/articles/go-walk-dir-get-all-file-paths/