Skip to content

Go 文件处理

文件处理是编程中的重要技能。Go 语言提供了丰富的标准库来处理文件和目录操作,包括读写文件、目录遍历、文件信息获取等功能。

📋 文件基础操作

创建和写入文件

go
package main

import (
    "fmt"
    "os"
)

func createAndWriteFile() {
    fmt.Println("=== 创建和写入文件 ===")
    
    // 1. 使用 os.Create 创建文件
    file, err := os.Create("example.txt")
    if err != nil {
        fmt.Printf("创建文件失败: %v\n", err)
        return
    }
    defer file.Close() // 确保文件被关闭
    
    // 2. 写入字符串
    content := "Hello, Go File Handling!\n这是第二行内容。\n"
    bytesWritten, err := file.WriteString(content)
    if err != nil {
        fmt.Printf("写入文件失败: %v\n", err)
        return
    }
    
    fmt.Printf("成功写入 %d 字节到文件\n", bytesWritten)
    
    // 3. 写入字节数据
    data := []byte("这是字节数据\n")
    bytesWritten, err = file.Write(data)
    if err != nil {
        fmt.Printf("写入字节失败: %v\n", err)
        return
    }
    
    fmt.Printf("成功写入 %d 字节数据\n", bytesWritten)
    
    // 4. 同步到磁盘
    err = file.Sync()
    if err != nil {
        fmt.Printf("同步文件失败: %v\n", err)
        return
    }
    
    fmt.Println("文件写入完成")
}

func main() {
    createAndWriteFile()
}

读取文件

go
package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func readFile() {
    fmt.Println("=== 读取文件 ===")
    
    // 1. 一次性读取整个文件
    fmt.Println("方法1: 一次性读取整个文件")
    content, err := os.ReadFile("example.txt")
    if err != nil {
        fmt.Printf("读取文件失败: %v\n", err)
        return
    }
    
    fmt.Printf("文件内容:\n%s\n", content)
    
    // 2. 使用 os.Open 和 Read
    fmt.Println("方法2: 使用 os.Open 逐块读取")
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Printf("打开文件失败: %v\n", err)
        return
    }
    defer file.Close()
    
    buffer := make([]byte, 1024)
    for {
        bytesRead, err := file.Read(buffer)
        if err == io.EOF {
            break // 文件读取完毕
        }
        if err != nil {
            fmt.Printf("读取文件错误: %v\n", err)
            break
        }
        
        fmt.Printf("读取 %d 字节: %s", bytesRead, buffer[:bytesRead])
    }
    
    // 3. 使用 bufio.Scanner 逐行读取
    fmt.Println("\n方法3: 逐行读取")
    file, err = os.Open("example.txt")
    if err != nil {
        fmt.Printf("打开文件失败: %v\n", err)
        return
    }
    defer file.Close()
    
    scanner := bufio.NewScanner(file)
    lineNumber := 1
    
    for scanner.Scan() {
        line := scanner.Text()
        fmt.Printf("第 %d 行: %s\n", lineNumber, line)
        lineNumber++
    }
    
    if err := scanner.Err(); err != nil {
        fmt.Printf("扫描文件错误: %v\n", err)
    }
}

func main() {
    readFile()
}

📁 目录操作

目录创建和遍历

go
package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func directoryOperations() {
    fmt.Println("=== 目录操作 ===")
    
    // 1. 创建目录
    dirName := "test_directory"
    
    err := os.Mkdir(dirName, 0755) // 0755 是权限模式
    if err != nil && !os.IsExist(err) {
        fmt.Printf("创建目录失败: %v\n", err)
        return
    }
    fmt.Printf("目录 '%s' 创建成功\n", dirName)
    
    // 2. 创建嵌套目录
    nestedDir := "parent/child/grandchild"
    err = os.MkdirAll(nestedDir, 0755)
    if err != nil {
        fmt.Printf("创建嵌套目录失败: %v\n", err)
        return
    }
    fmt.Printf("嵌套目录 '%s' 创建成功\n", nestedDir)
    
    // 3. 在目录中创建文件
    testFiles := []string{
        filepath.Join(dirName, "file1.txt"),
        filepath.Join(dirName, "file2.log"),
        filepath.Join(dirName, "data.json"),
        filepath.Join(nestedDir, "nested_file.txt"),
    }
    
    for _, filename := range testFiles {
        file, err := os.Create(filename)
        if err != nil {
            fmt.Printf("创建文件 %s 失败: %v\n", filename, err)
            continue
        }
        
        file.WriteString(fmt.Sprintf("这是文件 %s 的内容\n", filename))
        file.Close()
        fmt.Printf("文件 '%s' 创建成功\n", filename)
    }
    
    // 4. 列出目录内容
    fmt.Printf("\n'%s' 目录内容:\n", dirName)
    entries, err := os.ReadDir(dirName)
    if err != nil {
        fmt.Printf("读取目录失败: %v\n", err)
        return
    }
    
    for _, entry := range entries {
        if entry.IsDir() {
            fmt.Printf("  📁 %s (目录)\n", entry.Name())
        } else {
            info, _ := entry.Info()
            fmt.Printf("  📄 %s (文件, %d 字节)\n", entry.Name(), info.Size())
        }
    }
}

// 递归遍历目录
func walkDirectory(rootDir string) {
    fmt.Printf("\n=== 递归遍历目录 '%s' ===\n", rootDir)
    
    err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            fmt.Printf("访问 %s 时出错: %v\n", path, err)
            return nil // 继续遍历其他文件
        }
        
        // 计算相对路径的层级
        relPath, _ := filepath.Rel(rootDir, path)
        level := len(filepath.SplitList(relPath)) - 1
        indent := ""
        for i := 0; i < level; i++ {
            indent += "  "
        }
        
        if info.IsDir() {
            fmt.Printf("%s📁 %s/\n", indent, info.Name())
        } else {
            fmt.Printf("%s📄 %s (%d 字节)\n", indent, info.Name(), info.Size())
        }
        
        return nil
    })
    
    if err != nil {
        fmt.Printf("遍历目录失败: %v\n", err)
    }
}

func main() {
    directoryOperations()
    walkDirectory(".")
    
    // 清理测试文件和目录
    fmt.Println("\n=== 清理测试文件 ===")
    os.RemoveAll("test_directory")
    os.RemoveAll("parent")
    os.Remove("example.txt")
    fmt.Println("清理完成")
}

📄 文件信息和权限

获取文件信息

go
package main

import (
    "fmt"
    "os"
    "time"
)

func fileInfo() {
    fmt.Println("=== 文件信息获取 ===")
    
    // 创建测试文件
    filename := "info_test.txt"
    content := "这是用于测试文件信息的文件内容。\n包含多行文本。\n"
    
    err := os.WriteFile(filename, []byte(content), 0644)
    if err != nil {
        fmt.Printf("创建测试文件失败: %v\n", err)
        return
    }
    
    // 获取文件信息
    info, err := os.Stat(filename)
    if err != nil {
        fmt.Printf("获取文件信息失败: %v\n", err)
        return
    }
    
    fmt.Printf("文件名: %s\n", info.Name())
    fmt.Printf("文件大小: %d 字节\n", info.Size())
    fmt.Printf("文件权限: %s\n", info.Mode())
    fmt.Printf("修改时间: %s\n", info.ModTime().Format("2006-01-02 15:04:05"))
    fmt.Printf("是否为目录: %v\n", info.IsDir())
    
    // 检查文件是否存在
    fmt.Printf("\n=== 文件存在性检查 ===\n")
    
    files := []string{filename, "不存在的文件.txt", ".", "/etc/passwd"}
    
    for _, file := range files {
        if _, err := os.Stat(file); err == nil {
            fmt.Printf("✅ '%s' 存在\n", file)
        } else if os.IsNotExist(err) {
            fmt.Printf("❌ '%s' 不存在\n", file)
        } else {
            fmt.Printf("⚠️ '%s' 检查时出错: %v\n", file, err)
        }
    }
    
    // 修改文件权限
    fmt.Printf("\n=== 修改文件权限 ===\n")
    fmt.Printf("原始权限: %s\n", info.Mode())
    
    err = os.Chmod(filename, 0755)
    if err != nil {
        fmt.Printf("修改权限失败: %v\n", err)
    } else {
        newInfo, _ := os.Stat(filename)
        fmt.Printf("新权限: %s\n", newInfo.Mode())
    }
    
    // 修改文件时间
    fmt.Printf("\n=== 修改文件时间 ===\n")
    fmt.Printf("原始修改时间: %s\n", info.ModTime().Format("2006-01-02 15:04:05"))
    
    newTime := time.Now().Add(-24 * time.Hour) // 设置为24小时前
    err = os.Chtimes(filename, newTime, newTime)
    if err != nil {
        fmt.Printf("修改时间失败: %v\n", err)
    } else {
        updatedInfo, _ := os.Stat(filename)
        fmt.Printf("新修改时间: %s\n", updatedInfo.ModTime().Format("2006-01-02 15:04:05"))
    }
    
    // 清理
    os.Remove(filename)
}

func main() {
    fileInfo()
}

🔄 文件操作进阶

文件复制和移动

go
package main

import (
    "fmt"
    "io"
    "os"
    "path/filepath"
)

// 复制文件
func copyFile(src, dst string) error {
    sourceFile, err := os.Open(src)
    if err != nil {
        return err
    }
    defer sourceFile.Close()
    
    destFile, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer destFile.Close()
    
    _, err = io.Copy(destFile, sourceFile)
    if err != nil {
        return err
    }
    
    // 同步到磁盘
    return destFile.Sync()
}

// 复制文件并保持权限
func copyFileWithPerms(src, dst string) error {
    srcInfo, err := os.Stat(src)
    if err != nil {
        return err
    }
    
    err = copyFile(src, dst)
    if err != nil {
        return err
    }
    
    // 设置相同的权限
    return os.Chmod(dst, srcInfo.Mode())
}

// 移动文件
func moveFile(src, dst string) error {
    err := os.Rename(src, dst)
    if err != nil {
        // 如果重命名失败,尝试复制后删除
        err = copyFile(src, dst)
        if err != nil {
            return err
        }
        return os.Remove(src)
    }
    return nil
}

func advancedFileOps() {
    fmt.Println("=== 高级文件操作 ===")
    
    // 创建测试文件
    srcFile := "source.txt"
    content := "这是源文件的内容\n包含多行数据\n用于测试文件操作"
    
    err := os.WriteFile(srcFile, []byte(content), 0644)
    if err != nil {
        fmt.Printf("创建源文件失败: %v\n", err)
        return
    }
    
    // 1. 复制文件
    dstFile := "destination.txt"
    fmt.Printf("复制文件: %s -> %s\n", srcFile, dstFile)
    
    err = copyFileWithPerms(srcFile, dstFile)
    if err != nil {
        fmt.Printf("复制文件失败: %v\n", err)
        return
    }
    
    // 验证复制结果
    srcInfo, _ := os.Stat(srcFile)
    dstInfo, _ := os.Stat(dstFile)
    
    fmt.Printf("源文件大小: %d 字节\n", srcInfo.Size())
    fmt.Printf("目标文件大小: %d 字节\n", dstInfo.Size())
    fmt.Printf("复制成功: %v\n", srcInfo.Size() == dstInfo.Size())
    
    // 2. 移动文件
    movedFile := "moved.txt"
    fmt.Printf("\n移动文件: %s -> %s\n", dstFile, movedFile)
    
    err = moveFile(dstFile, movedFile)
    if err != nil {
        fmt.Printf("移动文件失败: %v\n", err)
        return
    }
    
    // 验证移动结果
    if _, err := os.Stat(dstFile); os.IsNotExist(err) {
        fmt.Println("原文件已不存在 ✅")
    }
    
    if _, err := os.Stat(movedFile); err == nil {
        fmt.Println("新文件存在 ✅")
    }
    
    // 3. 创建临时文件
    fmt.Println("\n=== 临时文件操作 ===")
    
    tempFile, err := os.CreateTemp("", "go_temp_*.txt")
    if err != nil {
        fmt.Printf("创建临时文件失败: %v\n", err)
        return
    }
    
    tempFileName := tempFile.Name()
    fmt.Printf("临时文件路径: %s\n", tempFileName)
    
    // 写入临时文件
    tempFile.WriteString("这是临时文件内容")
    tempFile.Close()
    
    // 读取临时文件
    tempContent, err := os.ReadFile(tempFileName)
    if err != nil {
        fmt.Printf("读取临时文件失败: %v\n", err)
    } else {
        fmt.Printf("临时文件内容: %s\n", tempContent)
    }
    
    // 清理文件
    fmt.Println("\n=== 清理测试文件 ===")
    files := []string{srcFile, movedFile, tempFileName}
    
    for _, file := range files {
        if err := os.Remove(file); err != nil {
            fmt.Printf("删除 %s 失败: %v\n", file, err)
        } else {
            fmt.Printf("删除 %s 成功\n", file)
        }
    }
}

func main() {
    advancedFileOps()
}

🎯 实际应用示例

日志文件管理器

go
package main

import (
    "fmt"
    "os"
    "path/filepath"
    "sort"
    "strings"
    "time"
)

// 日志文件管理器
type LogManager struct {
    logDir      string
    maxFiles    int
    maxSizeMB   int64
    filePattern string
}

// 创建日志管理器
func NewLogManager(dir string, maxFiles int, maxSizeMB int64) *LogManager {
    return &LogManager{
        logDir:      dir,
        maxFiles:    maxFiles,
        maxSizeMB:   maxSizeMB,
        filePattern: "app_*.log",
    }
}

// 写入日志
func (lm *LogManager) WriteLog(message string) error {
    // 确保日志目录存在
    err := os.MkdirAll(lm.logDir, 0755)
    if err != nil {
        return err
    }
    
    // 获取当前日志文件
    logFile := lm.getCurrentLogFile()
    
    // 检查文件大小
    if lm.shouldRotateLog(logFile) {
        err = lm.rotateLog()
        if err != nil {
            return err
        }
        logFile = lm.getCurrentLogFile()
    }
    
    // 写入日志
    file, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        return err
    }
    defer file.Close()
    
    timestamp := time.Now().Format("2006-01-02 15:04:05")
    logEntry := fmt.Sprintf("[%s] %s\n", timestamp, message)
    
    _, err = file.WriteString(logEntry)
    return err
}

// 获取当前日志文件路径
func (lm *LogManager) getCurrentLogFile() string {
    return filepath.Join(lm.logDir, "app_current.log")
}

// 检查是否需要轮转日志
func (lm *LogManager) shouldRotateLog(filename string) bool {
    info, err := os.Stat(filename)
    if err != nil {
        return false // 文件不存在,不需要轮转
    }
    
    sizeMB := info.Size() / (1024 * 1024)
    return sizeMB >= lm.maxSizeMB
}

// 轮转日志文件
func (lm *LogManager) rotateLog() error {
    currentFile := lm.getCurrentLogFile()
    
    // 检查当前文件是否存在
    if _, err := os.Stat(currentFile); os.IsNotExist(err) {
        return nil // 文件不存在,无需轮转
    }
    
    // 生成新的文件名
    timestamp := time.Now().Format("20060102_150405")
    rotatedFile := filepath.Join(lm.logDir, fmt.Sprintf("app_%s.log", timestamp))
    
    // 重命名当前文件
    err := os.Rename(currentFile, rotatedFile)
    if err != nil {
        return err
    }
    
    fmt.Printf("日志文件已轮转: %s\n", rotatedFile)
    
    // 清理旧文件
    return lm.cleanOldLogs()
}

// 清理旧的日志文件
func (lm *LogManager) cleanOldLogs() error {
    files, err := filepath.Glob(filepath.Join(lm.logDir, lm.filePattern))
    if err != nil {
        return err
    }
    
    // 按修改时间排序
    type fileInfo struct {
        path    string
        modTime time.Time
    }
    
    var fileInfos []fileInfo
    for _, file := range files {
        info, err := os.Stat(file)
        if err != nil {
            continue
        }
        fileInfos = append(fileInfos, fileInfo{
            path:    file,
            modTime: info.ModTime(),
        })
    }
    
    // 按时间降序排序(最新的在前)
    sort.Slice(fileInfos, func(i, j int) bool {
        return fileInfos[i].modTime.After(fileInfos[j].modTime)
    })
    
    // 删除超出限制的文件
    if len(fileInfos) > lm.maxFiles {
        for i := lm.maxFiles; i < len(fileInfos); i++ {
            err := os.Remove(fileInfos[i].path)
            if err != nil {
                fmt.Printf("删除旧日志文件失败 %s: %v\n", fileInfos[i].path, err)
            } else {
                fmt.Printf("删除旧日志文件: %s\n", fileInfos[i].path)
            }
        }
    }
    
    return nil
}

// 获取日志统计信息
func (lm *LogManager) GetStats() (map[string]interface{}, error) {
    files, err := filepath.Glob(filepath.Join(lm.logDir, "app_*.log"))
    if err != nil {
        return nil, err
    }
    
    var totalSize int64
    var fileCount int
    var oldestFile, newestFile time.Time
    
    for i, file := range files {
        info, err := os.Stat(file)
        if err != nil {
            continue
        }
        
        totalSize += info.Size()
        fileCount++
        
        if i == 0 {
            oldestFile = info.ModTime()
            newestFile = info.ModTime()
        } else {
            if info.ModTime().Before(oldestFile) {
                oldestFile = info.ModTime()
            }
            if info.ModTime().After(newestFile) {
                newestFile = info.ModTime()
            }
        }
    }
    
    stats := map[string]interface{}{
        "file_count":   fileCount,
        "total_size_mb": float64(totalSize) / (1024 * 1024),
        "oldest_file":  oldestFile.Format("2006-01-02 15:04:05"),
        "newest_file":  newestFile.Format("2006-01-02 15:04:05"),
    }
    
    return stats, nil
}

func demonstrateLogManager() {
    fmt.Println("=== 日志文件管理器演示 ===")
    
    // 创建日志管理器
    logManager := NewLogManager("logs", 3, 1) // 最多3个文件,每个文件最大1MB
    
    // 写入一些日志
    messages := []string{
        "应用程序启动",
        "用户登录: user123",
        "数据库连接成功",
        "处理用户请求",
        "生成报告完成",
        "用户退出登录",
        "应用程序关闭",
    }
    
    for _, msg := range messages {
        err := logManager.WriteLog(msg)
        if err != nil {
            fmt.Printf("写入日志失败: %v\n", err)
            continue
        }
        fmt.Printf("✅ 日志写入: %s\n", msg)
        time.Sleep(100 * time.Millisecond) // 模拟时间间隔
    }
    
    // 模拟大量日志写入以触发轮转
    fmt.Println("\n模拟大量日志写入...")
    for i := 0; i < 100; i++ {
        longMessage := fmt.Sprintf("这是一条很长的日志消息 #%d,包含大量数据: %s", 
                                   i, strings.Repeat("data ", 50))
        logManager.WriteLog(longMessage)
        
        if i%20 == 0 {
            fmt.Printf("已写入 %d 条日志\n", i+1)
        }
    }
    
    // 获取统计信息
    fmt.Println("\n=== 日志统计信息 ===")
    stats, err := logManager.GetStats()
    if err != nil {
        fmt.Printf("获取统计信息失败: %v\n", err)
    } else {
        for key, value := range stats {
            fmt.Printf("%s: %v\n", key, value)
        }
    }
    
    // 列出所有日志文件
    fmt.Println("\n=== 日志文件列表 ===")
    files, err := filepath.Glob("logs/app_*.log")
    if err != nil {
        fmt.Printf("获取文件列表失败: %v\n", err)
    } else {
        for _, file := range files {
            info, _ := os.Stat(file)
            fmt.Printf("📄 %s (%.2f MB)\n", 
                      filepath.Base(file), float64(info.Size())/(1024*1024))
        }
    }
    
    // 清理演示文件
    fmt.Println("\n=== 清理演示文件 ===")
    os.RemoveAll("logs")
    fmt.Println("清理完成")
}

func main() {
    demonstrateLogManager()
}

🎓 小结

本章我们全面学习了 Go 语言的文件处理:

  • 文件基础:创建、读取、写入文件的多种方法
  • 目录操作:目录创建、遍历和管理
  • 文件信息:获取文件属性、权限设置
  • 高级操作:文件复制、移动、临时文件
  • 实际应用:日志文件管理器的完整实现

掌握文件处理技能对于开发实际应用程序非常重要,特别是在处理配置文件、日志文件、数据文件等场景中。


接下来,我们将学习 Go 语言正则表达式,掌握强大的文本模式匹配工具。

文件处理建议

  • 总是记得关闭文件,使用 defer 确保资源释放
  • 合理处理文件操作中的错误
  • 注意文件权限设置,遵循最小权限原则
  • 对于大文件,考虑使用流式处理避免内存问题

本站内容仅供学习和研究使用。