4.1.2 文件系统遍历 #
文件系统遍历是系统编程中的重要技能,它允许我们递归地访问目录结构中的所有文件和子目录。Go 语言提供了多种方式来实现文件系统遍历,从简单的目录读取到高效的递归遍历。本节将详细介绍各种文件系统遍历技术和最佳实践。
目录操作基础 #
读取目录内容 #
Go 语言提供了多种方法来读取目录内容:
package main
import (
"fmt"
"os"
)
func readDirectoryBasic() {
// 方法1: 使用 os.ReadDir (Go 1.16+)
entries, err := os.ReadDir(".")
if err != nil {
fmt.Printf("读取目录失败: %v\n", err)
return
}
fmt.Println("当前目录内容:")
for _, entry := range entries {
if entry.IsDir() {
fmt.Printf("[DIR] %s\n", entry.Name())
} else {
fmt.Printf("[FILE] %s\n", entry.Name())
}
}
}
func readDirectoryWithInfo() {
// 方法2: 使用 os.File.Readdir
dir, err := os.Open(".")
if err != nil {
fmt.Printf("打开目录失败: %v\n", err)
return
}
defer dir.Close()
fileInfos, err := dir.Readdir(-1) // -1 表示读取所有条目
if err != nil {
fmt.Printf("读取目录信息失败: %v\n", err)
return
}
fmt.Println("目录详细信息:")
for _, info := range fileInfos {
fmt.Printf("名称: %s, 大小: %d, 修改时间: %v, 是否目录: %v\n",
info.Name(), info.Size(), info.ModTime(), info.IsDir())
}
}
创建和删除目录 #
func directoryOperations() {
// 创建单个目录
err := os.Mkdir("test_dir", 0755)
if err != nil {
fmt.Printf("创建目录失败: %v\n", err)
} else {
fmt.Println("目录创建成功")
}
// 创建多级目录
err = os.MkdirAll("path/to/nested/dir", 0755)
if err != nil {
fmt.Printf("创建多级目录失败: %v\n", err)
} else {
fmt.Println("多级目录创建成功")
}
// 删除空目录
err = os.Remove("test_dir")
if err != nil {
fmt.Printf("删除目录失败: %v\n", err)
} else {
fmt.Println("目录删除成功")
}
// 递归删除目录及其内容
err = os.RemoveAll("path")
if err != nil {
fmt.Printf("递归删除失败: %v\n", err)
} else {
fmt.Println("递归删除成功")
}
}
使用 filepath.Walk 进行遍历 #
基础遍历 #
filepath.Walk
是 Go 语言中最常用的文件系统遍历函数:
package main
import (
"fmt"
"os"
"path/filepath"
)
func basicWalk() {
root := "."
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Printf("访问 %s 时出错: %v\n", path, err)
return err
}
// 打印文件信息
if info.IsDir() {
fmt.Printf("[DIR] %s\n", path)
} else {
fmt.Printf("[FILE] %s (大小: %d 字节)\n", path, info.Size())
}
return nil
})
if err != nil {
fmt.Printf("遍历失败: %v\n", err)
}
}
高级遍历示例 #
func advancedWalk() {
root := "/path/to/search"
var totalSize int64
fileCount := 0
dirCount := 0
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
// 记录错误但继续遍历
fmt.Printf("警告: 无法访问 %s: %v\n", path, err)
return nil
}
if info.IsDir() {
dirCount++
fmt.Printf("进入目录: %s\n", path)
} else {
fileCount++
totalSize += info.Size()
// 根据文件扩展名进行分类
ext := filepath.Ext(path)
switch ext {
case ".go":
fmt.Printf("Go 源文件: %s\n", path)
case ".txt":
fmt.Printf("文本文件: %s\n", path)
case ".jpg", ".png", ".gif":
fmt.Printf("图片文件: %s\n", path)
default:
fmt.Printf("其他文件: %s\n", path)
}
}
return nil
})
if err != nil {
fmt.Printf("遍历失败: %v\n", err)
return
}
fmt.Printf("\n遍历统计:\n")
fmt.Printf("目录数量: %d\n", dirCount)
fmt.Printf("文件数量: %d\n", fileCount)
fmt.Printf("总大小: %d 字节 (%.2f MB)\n", totalSize, float64(totalSize)/(1024*1024))
}
条件过滤遍历 #
func filteredWalk() {
root := "."
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 跳过隐藏文件和目录
if filepath.Base(path)[0] == '.' && path != root {
if info.IsDir() {
return filepath.SkipDir // 跳过整个目录
}
return nil // 跳过文件
}
// 只处理 Go 源文件
if !info.IsDir() && filepath.Ext(path) == ".go" {
fmt.Printf("Go 文件: %s\n", path)
// 可以在这里进行文件内容分析
analyzeGoFile(path)
}
return nil
})
if err != nil {
fmt.Printf("遍历失败: %v\n", err)
}
}
func analyzeGoFile(path string) {
file, err := os.Open(path)
if err != nil {
fmt.Printf("无法打开文件 %s: %v\n", path, err)
return
}
defer file.Close()
// 简单的行数统计
scanner := bufio.NewScanner(file)
lineCount := 0
for scanner.Scan() {
lineCount++
}
fmt.Printf(" -> %d 行代码\n", lineCount)
}
使用 filepath.WalkDir 进行高效遍历 #
Go 1.16 引入了 filepath.WalkDir
,它比 filepath.Walk
更高效:
func efficientWalk() {
root := "."
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
fmt.Printf("访问 %s 时出错: %v\n", path, err)
return err
}
if d.IsDir() {
fmt.Printf("[DIR] %s\n", path)
} else {
// 只在需要时获取详细信息
info, err := d.Info()
if err != nil {
fmt.Printf("获取文件信息失败: %v\n", err)
return nil
}
fmt.Printf("[FILE] %s (大小: %d 字节)\n", path, info.Size())
}
return nil
})
if err != nil {
fmt.Printf("遍历失败: %v\n", err)
}
}
自定义遍历实现 #
递归遍历实现 #
func customWalk(root string, visitFunc func(path string, info os.FileInfo) error) error {
return customWalkRecursive(root, visitFunc)
}
func customWalkRecursive(path string, visitFunc func(string, os.FileInfo) error) error {
info, err := os.Stat(path)
if err != nil {
return err
}
// 访问当前路径
if err := visitFunc(path, info); err != nil {
return err
}
// 如果是目录,递归遍历子项
if info.IsDir() {
entries, err := os.ReadDir(path)
if err != nil {
return err
}
for _, entry := range entries {
childPath := filepath.Join(path, entry.Name())
if err := customWalkRecursive(childPath, visitFunc); err != nil {
return err
}
}
}
return nil
}
func testCustomWalk() {
err := customWalk(".", func(path string, info os.FileInfo) error {
if info.IsDir() {
fmt.Printf("[DIR] %s\n", path)
} else {
fmt.Printf("[FILE] %s\n", path)
}
return nil
})
if err != nil {
fmt.Printf("自定义遍历失败: %v\n", err)
}
}
并发遍历实现 #
package main
import (
"fmt"
"os"
"path/filepath"
"sync"
)
type WalkResult struct {
Path string
Info os.FileInfo
Err error
}
func concurrentWalk(root string, workers int) <-chan WalkResult {
results := make(chan WalkResult)
paths := make(chan string)
// 启动工作协程
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for path := range paths {
info, err := os.Stat(path)
results <- WalkResult{
Path: path,
Info: info,
Err: err,
}
}
}()
}
// 启动路径发现协程
go func() {
defer close(paths)
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
results <- WalkResult{Path: path, Err: err}
return nil
}
paths <- path
return nil
})
}()
// 启动结果关闭协程
go func() {
wg.Wait()
close(results)
}()
return results
}
func testConcurrentWalk() {
results := concurrentWalk(".", 4)
for result := range results {
if result.Err != nil {
fmt.Printf("错误: %s - %v\n", result.Path, result.Err)
continue
}
if result.Info.IsDir() {
fmt.Printf("[DIR] %s\n", result.Path)
} else {
fmt.Printf("[FILE] %s\n", result.Path)
}
}
}
实际应用示例 #
文件搜索工具 #
package main
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
type SearchCriteria struct {
NamePattern *regexp.Regexp
SizeMin int64
SizeMax int64
ModifiedAfter time.Time
ModifiedBefore time.Time
FileType string // "file", "dir", "all"
}
type SearchResult struct {
Path string
Info os.FileInfo
Matches []string
}
func searchFiles(root string, criteria SearchCriteria) ([]SearchResult, error) {
var results []SearchResult
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil // 跳过错误,继续遍历
}
// 检查文件类型
if criteria.FileType == "file" && info.IsDir() {
return nil
}
if criteria.FileType == "dir" && !info.IsDir() {
return nil
}
// 检查名称模式
if criteria.NamePattern != nil {
if !criteria.NamePattern.MatchString(filepath.Base(path)) {
return nil
}
}
// 检查文件大小
if !info.IsDir() {
if criteria.SizeMin > 0 && info.Size() < criteria.SizeMin {
return nil
}
if criteria.SizeMax > 0 && info.Size() > criteria.SizeMax {
return nil
}
}
// 检查修改时间
if !criteria.ModifiedAfter.IsZero() && info.ModTime().Before(criteria.ModifiedAfter) {
return nil
}
if !criteria.ModifiedBefore.IsZero() && info.ModTime().After(criteria.ModifiedBefore) {
return nil
}
// 符合条件,添加到结果
results = append(results, SearchResult{
Path: path,
Info: info,
})
return nil
})
return results, err
}
func main() {
// 搜索所有 .go 文件
namePattern, _ := regexp.Compile(`\.go$`)
criteria := SearchCriteria{
NamePattern: namePattern,
FileType: "file",
SizeMin: 100, // 至少 100 字节
}
results, err := searchFiles(".", criteria)
if err != nil {
fmt.Printf("搜索失败: %v\n", err)
return
}
fmt.Printf("找到 %d 个匹配的文件:\n", len(results))
for _, result := range results {
fmt.Printf("%s (大小: %d 字节, 修改时间: %v)\n",
result.Path, result.Info.Size(), result.Info.ModTime())
}
}
目录同步工具 #
type SyncOperation struct {
Type string // "copy", "delete", "update"
Src string
Dst string
Info os.FileInfo
}
func syncDirectories(srcDir, dstDir string) error {
operations, err := planSyncOperations(srcDir, dstDir)
if err != nil {
return err
}
fmt.Printf("计划执行 %d 个同步操作\n", len(operations))
for _, op := range operations {
if err := executeSyncOperation(op); err != nil {
fmt.Printf("执行操作失败: %v\n", err)
continue
}
fmt.Printf("完成: %s %s\n", op.Type, op.Src)
}
return nil
}
func planSyncOperations(srcDir, dstDir string) ([]SyncOperation, error) {
var operations []SyncOperation
srcFiles := make(map[string]os.FileInfo)
dstFiles := make(map[string]os.FileInfo)
// 扫描源目录
err := filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, _ := filepath.Rel(srcDir, path)
srcFiles[relPath] = info
return nil
})
if err != nil {
return nil, err
}
// 扫描目标目录
if _, err := os.Stat(dstDir); err == nil {
err = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, _ := filepath.Rel(dstDir, path)
dstFiles[relPath] = info
return nil
})
if err != nil {
return nil, err
}
}
// 计划操作
for relPath, srcInfo := range srcFiles {
srcPath := filepath.Join(srcDir, relPath)
dstPath := filepath.Join(dstDir, relPath)
if dstInfo, exists := dstFiles[relPath]; exists {
// 文件存在,检查是否需要更新
if !srcInfo.IsDir() && (srcInfo.Size() != dstInfo.Size() ||
srcInfo.ModTime().After(dstInfo.ModTime())) {
operations = append(operations, SyncOperation{
Type: "update",
Src: srcPath,
Dst: dstPath,
Info: srcInfo,
})
}
} else {
// 文件不存在,需要复制
operations = append(operations, SyncOperation{
Type: "copy",
Src: srcPath,
Dst: dstPath,
Info: srcInfo,
})
}
}
// 检查需要删除的文件
for relPath := range dstFiles {
if _, exists := srcFiles[relPath]; !exists {
operations = append(operations, SyncOperation{
Type: "delete",
Dst: filepath.Join(dstDir, relPath),
})
}
}
return operations, nil
}
func executeSyncOperation(op SyncOperation) error {
switch op.Type {
case "copy", "update":
if op.Info.IsDir() {
return os.MkdirAll(op.Dst, op.Info.Mode())
} else {
return copyFile(op.Src, op.Dst)
}
case "delete":
return os.RemoveAll(op.Dst)
}
return nil
}
func copyFile(src, dst string) error {
// 确保目标目录存在
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return err
}
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
return err
}
性能优化技巧 #
避免重复的 Stat 调用 #
func optimizedWalk() {
err := filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
// 只在需要详细信息时调用 Info()
if d.IsDir() {
fmt.Printf("[DIR] %s\n", path)
} else {
// 对于文件,只在需要时获取详细信息
if strings.HasSuffix(path, ".log") {
info, err := d.Info()
if err != nil {
return err
}
fmt.Printf("[LOG] %s (大小: %d)\n", path, info.Size())
} else {
fmt.Printf("[FILE] %s\n", path)
}
}
return nil
})
if err != nil {
fmt.Printf("遍历失败: %v\n", err)
}
}
使用缓存减少系统调用 #
type CachedWalker struct {
cache map[string]os.FileInfo
mutex sync.RWMutex
}
func NewCachedWalker() *CachedWalker {
return &CachedWalker{
cache: make(map[string]os.FileInfo),
}
}
func (cw *CachedWalker) GetInfo(path string) (os.FileInfo, error) {
cw.mutex.RLock()
if info, exists := cw.cache[path]; exists {
cw.mutex.RUnlock()
return info, nil
}
cw.mutex.RUnlock()
info, err := os.Stat(path)
if err != nil {
return nil, err
}
cw.mutex.Lock()
cw.cache[path] = info
cw.mutex.Unlock()
return info, nil
}
小结 #
本节详细介绍了 Go 语言中的文件系统遍历技术,包括:
- 目录操作基础 - 读取、创建和删除目录的基本方法
- filepath.Walk - 标准库提供的递归遍历函数
- filepath.WalkDir - 更高效的遍历方法
- 自定义遍历 - 实现定制化的遍历逻辑
- 并发遍历 - 提高大型目录结构的遍历性能
- 实际应用 - 文件搜索和目录同步等实用工具
- 性能优化 - 减少系统调用和提高遍历效率的技巧
掌握这些技术后,你就能够高效地处理各种文件系统遍历需求。在下一节中,我们将学习文件权限与属性的管理。