← 返回目录

第七章:文件与IO

文件读写、目录遍历与数据处理

1 文件读写

Go 提供两种文件读写方式:os.ReadFile / os.WriteFile 适合小文件一次性读写;os.Open / os.Create 适合大文件或需要流式处理的场景。

简单方式 — 一次性读写

package main

import (
    "fmt"
    "os"
)

func main() {
    // 写入文件(文件不存在则创建,存在则覆盖)
    content := []byte("Hello, Go!\n这是文件内容。\n")
    err := os.WriteFile("output.txt", content, 0644)
    if err != nil {
        fmt.Println("写入失败:", err)
        return
    }

    // 读取整个文件到内存
    data, err := os.ReadFile("output.txt")
    if err != nil {
        fmt.Println("读取失败:", err)
        return
    }
    fmt.Println(string(data))
}

流式方式 — os.Open / os.Create

package main

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

func main() {
    // 创建/覆盖文件
    file, err := os.Create("stream.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    file.WriteString("第一行\n")
    file.WriteString("第二行\n")
    file.Write([]byte("第三行\n"))

    // 打开文件读取
    f, err := os.Open("stream.txt")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    data, err := io.ReadAll(f)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(data))

    // 追加模式打开文件
    af, err := os.OpenFile("stream.txt", os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        panic(err)
    }
    defer af.Close()
    af.WriteString("追加的内容\n")
}

文件信息与判断

// 检查文件是否存在
func fileExists(path string) bool {
    _, err := os.Stat(path)
    return !os.IsNotExist(err)
}

// 获取文件信息
info, err := os.Stat("output.txt")
if err == nil {
    fmt.Println("文件名:", info.Name())
    fmt.Println("大小:", info.Size(), "字节")
    fmt.Println("修改时间:", info.ModTime())
    fmt.Println("是否目录:", info.IsDir())
    fmt.Println("权限:", info.Mode())
}

始终 defer file.Close():打开文件后立即用 defer 关闭,确保函数退出时释放文件句柄,避免资源泄漏。

2 bufio 读写

bufio 包提供带缓冲的读写操作,大幅提升 IO 性能,特别适合按行处理大文件。

按行读取 — bufio.Scanner

package main

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

func main() {
    file, err := os.Open("data.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    lineNum := 0
    for scanner.Scan() {
        lineNum++
        fmt.Printf("%d: %s\n", lineNum, scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("读取错误:", err)
    }
}

bufio.Reader — 灵活读取

package main

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

func main() {
    file, err := os.Open("data.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    reader := bufio.NewReader(file)
    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            if err == io.EOF {
                if len(line) > 0 {
                    fmt.Print(line)
                }
                break
            }
            panic(err)
        }
        fmt.Print(line)
    }
}

bufio.Writer — 缓冲写入

package main

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

func main() {
    file, err := os.Create("buffered.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    writer := bufio.NewWriter(file)

    for i := 1; i <= 1000; i++ {
        fmt.Fprintf(writer, "第 %d 行数据\n", i)
    }

    // 必须调用 Flush 将缓冲区数据写入文件
    writer.Flush()
}

Scanner 默认缓冲区:默认最大 token 大小为 64KB。处理超长行时需要用 scanner.Buffer(buf, maxSize) 扩大缓冲区。

3 目录操作

创建目录

package main

import (
    "fmt"
    "os"
)

func main() {
    // 创建单层目录
    err := os.Mkdir("mydir", 0755)
    if err != nil {
        fmt.Println(err)
    }

    // 递归创建多层目录(类似 mkdir -p)
    err = os.MkdirAll("path/to/nested/dir", 0755)
    if err != nil {
        fmt.Println(err)
    }

    // 删除空目录
    os.Remove("mydir")

    // 递归删除目录及其内容
    os.RemoveAll("path")
}

读取目录内容 — os.ReadDir

package main

import (
    "fmt"
    "os"
)

func main() {
    entries, err := os.ReadDir(".")
    if err != nil {
        panic(err)
    }

    for _, entry := range entries {
        info, _ := entry.Info()
        if entry.IsDir() {
            fmt.Printf("[目录] %-20s\n", entry.Name())
        } else {
            fmt.Printf("[文件] %-20s %d 字节\n", entry.Name(), info.Size())
        }
    }
}

递归遍历 — filepath.WalkDir

package main

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

func main() {
    root := "."
    err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
        if err != nil {
            return err
        }

        // 跳过隐藏目录
        if d.IsDir() && path != root && path[0] == '.' {
            return filepath.SkipDir
        }

        indent := ""
        depth := len(filepath.SplitList(path))
        for i := 0; i < depth; i++ {
            indent += "  "
        }

        if d.IsDir() {
            fmt.Printf("%s📁 %s/\n", indent, d.Name())
        } else {
            fmt.Printf("%s📄 %s\n", indent, d.Name())
        }
        return nil
    })

    if err != nil {
        fmt.Println("遍历失败:", err)
    }
}

路径处理 — path/filepath

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // 拼接路径(自动处理分隔符)
    p := filepath.Join("home", "user", "documents", "file.txt")
    fmt.Println(p) // home/user/documents/file.txt

    // 获取目录和文件名
    fmt.Println(filepath.Dir(p))  // home/user/documents
    fmt.Println(filepath.Base(p)) // file.txt
    fmt.Println(filepath.Ext(p))  // .txt

    // 绝对路径
    abs, _ := filepath.Abs(".")
    fmt.Println("当前绝对路径:", abs)

    // 通配符匹配
    matches, _ := filepath.Glob("*.go")
    fmt.Println("Go 文件:", matches)

    // 相对路径
    rel, _ := filepath.Rel("/home/user", "/home/user/docs/file.txt")
    fmt.Println(rel) // docs/file.txt
}

4 JSON 文件处理

读写 JSON 文件

package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type Config struct {
    Host     string   `json:"host"`
    Port     int      `json:"port"`
    Debug    bool     `json:"debug"`
    AllowIPs []string `json:"allow_ips"`
}

func saveConfig(path string, cfg *Config) error {
    data, err := json.MarshalIndent(cfg, "", "  ")
    if err != nil {
        return err
    }
    return os.WriteFile(path, data, 0644)
}

func loadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, err
    }
    var cfg Config
    if err := json.Unmarshal(data, &cfg); err != nil {
        return nil, err
    }
    return &cfg, nil
}

func main() {
    cfg := &Config{
        Host:     "localhost",
        Port:     8080,
        Debug:    true,
        AllowIPs: []string{"127.0.0.1", "192.168.1.0/24"},
    }

    saveConfig("config.json", cfg)
    fmt.Println("配置已保存")

    loaded, err := loadConfig("config.json")
    if err != nil {
        panic(err)
    }
    fmt.Printf("加载配置: %s:%d, debug=%v\n", loaded.Host, loaded.Port, loaded.Debug)
}

流式处理大 JSON 文件

package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type Record struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func writeRecords(path string, records []Record) error {
    file, err := os.Create(path)
    if err != nil {
        return err
    }
    defer file.Close()

    encoder := json.NewEncoder(file)
    encoder.SetIndent("", "  ")
    return encoder.Encode(records)
}

func readRecords(path string) ([]Record, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var records []Record
    if err := json.NewDecoder(file).Decode(&records); err != nil {
        return nil, err
    }
    return records, nil
}

func main() {
    records := []Record{
        {ID: 1, Name: "张三"},
        {ID: 2, Name: "李四"},
        {ID: 3, Name: "王五"},
    }

    writeRecords("records.json", records)

    loaded, _ := readRecords("records.json")
    for _, r := range loaded {
        fmt.Printf("ID: %d, Name: %s\n", r.ID, r.Name)
    }
}

5 CSV 处理

encoding/csv 包提供 CSV 文件的读写支持,自动处理引号、转义等细节。

写入 CSV

package main

import (
    "encoding/csv"
    "os"
)

func main() {
    file, err := os.Create("users.csv")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // 写入 UTF-8 BOM(Excel 兼容)
    file.Write([]byte{0xEF, 0xBB, 0xBF})

    writer := csv.NewWriter(file)
    defer writer.Flush()

    // 写入表头
    writer.Write([]string{"ID", "姓名", "邮箱", "年龄"})

    // 写入数据行
    data := [][]string{
        {"1", "张三", "zhangsan@example.com", "28"},
        {"2", "李四", "lisi@example.com", "32"},
        {"3", "王五", "wangwu@example.com", "25"},
    }

    for _, row := range data {
        writer.Write(row)
    }
}

读取 CSV

package main

import (
    "encoding/csv"
    "fmt"
    "io"
    "os"
)

func main() {
    file, err := os.Open("users.csv")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    reader := csv.NewReader(file)

    // 读取全部数据
    // records, err := reader.ReadAll()

    // 逐行读取(适合大文件)
    header, err := reader.Read()
    if err != nil {
        panic(err)
    }
    fmt.Println("表头:", header)

    for {
        record, err := reader.Read()
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Println("读取错误:", err)
            continue
        }
        fmt.Printf("ID=%s, 姓名=%s, 邮箱=%s\n", record[0], record[1], record[2])
    }
}

自定义分隔符

// 使用 Tab 分隔的 TSV 文件
reader := csv.NewReader(file)
reader.Comma = '\t'
reader.Comment = '#'     // 以 # 开头的行视为注释
reader.TrimLeadingSpace = true

writer := csv.NewWriter(file)
writer.Comma = '\t'

CSV 编码注意:Go 默认使用 UTF-8,生成供 Excel 打开的 CSV 时建议在文件开头写入 BOM(0xEF, 0xBB, 0xBF),否则中文可能乱码。

6 本章要点

📄 文件读写

  • os.ReadFile / os.WriteFile 一次性
  • os.Open / os.Create 流式
  • os.OpenFile 追加模式

📖 bufio

  • bufio.Scanner 按行读取
  • bufio.NewReader 灵活读取
  • bufio.NewWriter + Flush()

📁 目录操作

  • os.MkdirAll 递归创建
  • os.ReadDir 列出内容
  • filepath.WalkDir 递归遍历

🗂️ 路径处理

  • filepath.Join 拼接路径
  • filepath.Dir / Base / Ext
  • filepath.Glob 通配符匹配

📋 JSON 文件

  • json.MarshalIndent 美化写入
  • json.NewEncoder / NewDecoder
  • • 配置文件读写场景

📊 CSV 处理

  • csv.NewReader / csv.NewWriter
  • • 自定义分隔符和注释符
  • • UTF-8 BOM 兼容 Excel