目录

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
  • 在包含上万个文件的目录中实测,WalkDirWalk 快 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 种:

  1. filepath.Walk — 兼容性最好,适合老版本 Go 项目。
  2. filepath.WalkDir — Go 1.16+ 推荐方案,性能优于 Walk,日常开发首选。
  3. 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/