Skip to content

4.6 文件与 IO 操作 ​

os 包文件系统操作 ​

文件基本操作 ​

go
package main

import (
    "fmt"
    "os"
)

func main() {
    // 创建文件
    file, err := os.Create("test.txt")
    if err != nil {
        fmt.Println("创建失败:", err)
        return
    }
    defer file.Close()

    // 写入数据
    file.WriteString("Hello, World!\n")
    file.Write([]byte("更多数据\n"))

    // 打开文件读取
    file2, err := os.Open("test.txt")
    if err != nil {
        fmt.Println("打开失败:", err)
        return
    }
    defer file2.Close()

    // 读取内容
    buf := make([]byte, 1024)
    n, _ := file2.Read(buf)
    fmt.Println("内容:", string(buf[:n]))

    // 追加模式打开
    file3, err := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        fmt.Println("打开失败:", err)
        return
    }
    defer file3.Close()
    file3.WriteString("追加的内容\n")

    // 删除文件
    // os.Remove("test.txt")

    // 重命名文件
    // os.Rename("old.txt", "new.txt")
}

文件信息操作 ​

go
package main

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

func main() {
    // 获取文件信息
    info, err := os.Stat("test.txt")
    if err != nil {
        fmt.Println("错误:", err)
        return
    }

    fmt.Println("文件名:", info.Name())
    fmt.Println("大小:", info.Size(), "bytes")
    fmt.Println("修改时间:", info.ModTime())
    fmt.Println("是否目录:", info.IsDir())
    fmt.Println("权限:", info.Mode())

    // 检查文件是否存在
    if _, err := os.Stat("test.txt"); os.IsNotExist(err) {
        fmt.Println("文件不存在")
    } else {
        fmt.Println("文件存在")
    }

    // 修改文件权限
    os.Chmod("test.txt", 0755)

    // 修改修改时间
    now := time.Now()
    os.Chtimes("test.txt", now, now)

    // 创建目录
    os.Mkdir("testdir", 0755)
    os.MkdirAll("a/b/c", 0755)  // 递归创建

    // 删除目录
    os.Remove("testdir")
    os.RemoveAll("a")  // 递归删除
}

目录操作 ​

go
package main

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

func main() {
    // 获取当前目录
    wd, _ := os.Getwd()
    fmt.Println("当前目录:", wd)

    // 切换目录
    os.Chdir("/tmp")

    // 读取目录内容
    entries, _ := os.ReadDir(".")
    for _, entry := range entries {
        fmt.Println(entry.Name(), entry.IsDir())
    }

    // 读取目录内容 (详细)
    infos, _ := os.ReadDir(".")
    for _, info := range infos {
        fmt.Println(info.Name())
    }

    // 遍历目录树
    filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        fmt.Printf("路径:%s, 类型:%s, 大小:%d\n",
            path,
            map[bool]string{true: "目录", false: "文件"}[info.IsDir()],
            info.Size())
        return nil
    })

    // Go 1.16+ 使用 WalkDir
    filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error {
        if err != nil {
            return err
        }
        fmt.Printf("路径:%s, 目录:%v\n", path, d.IsDir())
        return nil
    })
}

文件权限与模式 ​

go
package main

import (
    "fmt"
    "os"
)

func main() {
    // 文件模式常量
    // os.ModePerm      = 0777  // 文件权限位
    // os.ModeDir       = 1 << 32 - 1 << iota
    // os.ModeAppend    // 只能追加
    // os.ModeExclusive // 只能独占使用
    // os.ModeTemporary // 临时文件
    // os.ModeSymlink   // 符号链接
    // os.ModeDevice    // 设备文件
    // os.ModeSocket    // socket 文件

    // 创建带权限的文件
    file, _ := os.OpenFile("test.txt", os.O_CREATE|os.O_WRONLY, 0644)
    file.Close()

    // 修改权限
    os.Chmod("test.txt", 0755)

    // 检查权限
    info, _ := os.Stat("test.txt")
    mode := info.Mode()

    // 检查是否是目录
    if mode.IsDir() {
        fmt.Println("是目录")
    }

    // 检查权限位
    if mode&0200 != 0 {
        fmt.Println("所有者可写")
    }
}

io.Reader 与 io.Writer ​

基础接口 ​

go
package main

import (
    "io"
    "os"
    "strings"
)

// io.Reader 接口
// type Reader interface {
//     Read(p []byte) (n int, err error)
// }

// io.Writer 接口
// type Writer interface {
//     Write(p []byte) (n int, err error)
// }

// io.ReadWriter = Reader + Writer
// io.ReadWriteCloser = Reader + Writer + Closer

func main() {
    // 从字符串读取
    reader := strings.NewReader("Hello, World!")
    buf := make([]byte, 5)
    n, _ := reader.Read(buf)
    println(string(buf[:n]))  // Hello

    // 写入文件
    file, _ := os.Create("output.txt")
    defer file.Close()

    writer := io.Writer(file)
    writer.Write([]byte("test"))
}

io.Copy 与 io.Pipe ​

go
package main

import (
    "io"
    "os"
    "strings"
)

func main() {
    // io.Copy: 从 reader 复制到 writer
    src := strings.NewReader("Hello, World!")
    dst, _ := os.Create("output.txt")
    defer dst.Close()

    copied, _ := io.Copy(dst, src)
    println("复制了", copied, "字节")

    // io.CopyN: 复制指定字节数
    src2 := strings.NewReader("Hello, World!")
    dst2, _ := os.Create("output2.txt")
    defer dst2.Close()

    io.CopyN(dst2, src2, 5)  // 只复制 5 字节

    // io.Pipe: 创建管道 (同步的 in-memory pipe)
    reader, writer := io.Pipe()

    go func() {
        writer.Write([]byte("hello"))
        writer.Close()
    }()

    buf := make([]byte, 1024)
    n, _ := reader.Read(buf)
    println(string(buf[:n]))

    // io.MultiReader: 合并多个 reader
    mr := io.MultiReader(
        strings.NewReader("Hello, "),
        strings.NewReader("World!"),
    )
    io.Copy(os.Stdout, mr)  // 输出:Hello, World!

    // io.MultiWriter: 写入多个 destination
    mw := io.MultiWriter(os.Stdout, dst)
    mw.Write([]byte("test"))  // 同时输出到 stdout 和文件
}

自定义 Reader/Writer ​

go
package main

import (
    "errors"
    "io"
)

// 自定义 Reader
type CountingReader struct {
    r     io.Reader
    count int64
}

func (cr *CountingReader) Read(p []byte) (int, error) {
    n, err := cr.r.Read(p)
    cr.count += int64(n)
    return n, err
}

func (cr *CountingReader) Count() int64 {
    return cr.count
}

// 自定义 Writer
type LoggingWriter struct {
    w io.Writer
}

func (lw *LoggingWriter) Write(p []byte) (int, error) {
    println("写入:", len(p), "字节")
    return lw.w.Write(p)
}

// 实现 io.ReaderFrom / io.WriterTo 优化
type FastCopier struct{}

// WriterTo 接口可以让 io.Copy 使用更高效的方式
func (fc *FastCopier) WriteTo(w io.Writer) (int64, error) {
    // 直接写入,避免中间 buffer
    data := []byte("fast copy")
    return int64(len(data)), w.Write(data)
}

bufio 带缓冲的 IO ​

bufio.Reader ​

go
package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    // 创建带缓冲的 reader
    reader := bufio.NewReader(strings.NewReader("line1\nline2\nline3\n"))

    // 读取一行
    line, _ := reader.ReadString('\n')
    fmt.Println("第一行:", line)

    // 读取到分隔符 (返回 []byte)
    line2, _ := reader.ReadBytes('\n')
    fmt.Println("第二行:", string(line2))

    // 读取 rune
    r, _, _ := reader.ReadRune()
    fmt.Println("第一个字符:", string(r))

    // Peek: 查看接下来的 n 个字节 (不消耗)
    peeked, _ := reader.Peek(5)
    fmt.Println("Peek:", string(peeked))

    // Buffered: 返回缓冲中可用的字节数
    fmt.Println("缓冲中:", reader.Buffered(), "字节")

    // Discard: 跳过 n 个字节
    reader.Discard(2)
}

bufio.Writer ​

go
package main

import (
    "bufio"
    "os"
)

func main() {
    file, _ := os.Create("output.txt")
    defer file.Close()

    // 创建带缓冲的 writer
    writer := bufio.NewWriter(file)

    // 写入
    writer.WriteString("Hello, ")
    writer.WriteByte('W')
    writer.WriteString("orld!\n")

    // 刷新缓冲区
    writer.Flush()

    // 可用空间
    avail := writer.Available()
    println("可用空间:", avail)

    // 已缓冲但未刷新的字节数
    buffered := writer.Buffered()
    println("已缓冲:", buffered)
}

bufio.Scanner ​

go
package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    // 按行扫描
    scanner := bufio.NewScanner(strings.NewReader("line1\nline2\nline3"))

    for scanner.Scan() {
        line := scanner.Text()
        fmt.Println("行:", line)
    }

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

    // 自定义分割函数 (按单词扫描)
    scanner2 := bufio.NewScanner(strings.NewReader("hello world foo bar"))
    scanner2.Split(bufio.ScanWords)

    for scanner2.Scan() {
        fmt.Println("单词:", scanner2.Text())
    }

    // 内置分割函数
    // bufio.ScanLines   - 按行
    // bufio.ScanWords   - 按单词
    // bufio.ScanRunes   - 按 rune
    // bufio.ScanBytes   - 按字节

    // 自定义分割函数
    data := "a,b,c,d"
    scanner3 := bufio.NewScanner(strings.NewReader(data))

    // 按逗号分割
    scanner3.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        for i := 0; i < len(data); i++ {
            if data[i] == ',' {
                return i + 1, data[:i], nil
            }
        }
        if !atEOF {
            return 0, nil, nil
        }
        return len(data), data, nil
    })

    for scanner3.Scan() {
        fmt.Println("分割:", scanner3.Text())
    }
}

ioutil 便捷函数 ​

读取操作 ​

go
package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    // 读取整个文件
    data, err := ioutil.ReadFile("test.txt")
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    fmt.Println("内容:", string(data))

    // 读取目录条目
    entries, err := ioutil.ReadDir(".")
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    for _, entry := range entries {
        fmt.Println(entry.Name(), entry.Size())
    }

    // Go 1.16+ 推荐使用 os 包替代
    data2, _ := os.ReadFile("test.txt")      // 替代 ioutil.ReadFile
    entries2, _ := os.ReadDir(".")           // 替代 ioutil.ReadDir
}

写入操作 ​

go
package main

import (
    "io/ioutil"
    "os"
)

func main() {
    // 写入整个文件 (覆盖)
    ioutil.WriteFile("output.txt", []byte("Hello"), 0644)

    // Go 1.16+ 推荐使用 os 包替代
    os.WriteFile("output.txt", []byte("Hello"), 0644)

    // 创建临时文件
    tmpFile, err := ioutil.TempFile("", "prefix-")
    if err != nil {
        panic(err)
    }
    defer os.Remove(tmpFile.Name())  // 清理
    defer tmpFile.Close()

    tmpFile.WriteString("临时数据")

    // 创建临时目录
    tmpDir, err := ioutil.TempDir("", "prefix-")
    if err != nil {
        panic(err)
    }
    defer os.RemoveAll(tmpDir)  // 清理
}

路径处理 (filepath) ​

路径操作 ​

go
package main

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

func main() {
    // 路径分隔符
    fmt.Println("分隔符:", filepath.Separator)
    fmt.Println("系统:", runtime.GOOS)

    // 连接路径
    path := filepath.Join("home", "user", "docs")
    fmt.Println("连接:", path)

    // 清理路径
    clean := filepath.Clean("/home/../home/./user")
    fmt.Println("清理:", clean)

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

    // 规范路径 (清理 + 绝对路径)
    norm := filepath.Clean("/home/../home/user")
    fmt.Println("规范:", norm)

    // 路径分隔
    dir := filepath.Dir("/home/user/file.txt")
    fmt.Println("目录:", dir)

    base := filepath.Base("/home/user/file.txt")
    fmt.Println("文件名:", base)

    ext := filepath.Ext("/home/user/file.txt")
    fmt.Println("扩展名:", ext)

    // 分割扩展名
    name, ext2 := filepath.Split("file.txt")
    fmt.Println("名字:", name, "扩展:", ext2)
}

Glob 模式匹配 ​

go
package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // Glob: 查找匹配模式的文件
    matches, _ := filepath.Glob("*.go")
    fmt.Println("Go 文件:", matches)

    matches2, _ := filepath.Glob("*.md")
    fmt.Println("MD 文件:", matches2)

    // 匹配模式:
    // *     - 匹配任意非分隔符字符
    // ?     - 匹配单个非分隔符字符
    // [abc] - 匹配 a, b, 或 c
    // [^abc]- 匹配非 a, b, c 的字符
}

路径转换 ​

go
package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // 转为相对路径
    rel, _ := filepath.Rel("/home/user", "/home/user/docs/file.txt")
    fmt.Println("相对:", rel)

    // 转为绝对路径
    abs, _ := filepath.Abs("docs/file.txt")
    fmt.Println("绝对:", abs)

    // 卷名 (Windows)
    vol := filepath.VolumeName("C:\\Users\\file.txt")
    fmt.Println("卷名:", vol)

    // 匹配
    matched, _ := filepath.Match("*.txt", "file.txt")
    fmt.Println("匹配:", matched)
}

实用文件操作函数 ​

文件复制 ​

go
package main

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

// 复制文件
func CopyFile(src, dst string) error {
    source, err := os.Open(src)
    if err != nil {
        return err
    }
    defer source.Close()

    dest, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer dest.Close()

    _, err = io.Copy(dest, source)
    return err
}

// 复制目录
func CopyDir(src, dst string) error {
    entries, err := os.ReadDir(src)
    if err != nil {
        return err
    }

    os.MkdirAll(dst, 0755)

    for _, entry := range entries {
        srcPath := filepath.Join(src, entry.Name())
        dstPath := filepath.Join(dst, entry.Name())

        if entry.IsDir() {
            if err := CopyDir(srcPath, dstPath); err != nil {
                return err
            }
        } else {
            if err := CopyFile(srcPath, dstPath); err != nil {
                return err
            }
        }
    }
    return nil
}

文件查找 ​

go
package main

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

// 查找指定扩展名的文件
func FindFiles(dir, ext 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() && strings.HasSuffix(info.Name(), ext) {
            files = append(files, path)
        }
        return nil
    })
    return files, err
}

// 查找包含关键字的文件
func FindFilesWithContent(dir, keyword string) ([]string, error) {
    var result []string
    filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
        if err != nil || info.IsDir() {
            return err
        }
        data, _ := os.ReadFile(path)
        if strings.Contains(string(data), keyword) {
            result = append(result, path)
        }
        return nil
    })
    return result, nil
}

文件大小计算 ​

go
package main

import (
    "os"
    "path/filepath"
)

// 计算目录大小
func DirSize(path string) (int64, error) {
    var size int64
    err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        if !info.IsDir() {
            size += info.Size()
        }
        return nil
    })
    return size, err
}

// 格式化文件大小
func FormatFileSize(size int64) string {
    const (
        KB = 1024
        MB = KB * 1024
        GB = MB * 1024
    )

    switch {
    case size >= GB:
        return fmt.Sprintf("%.2f GB", float64(size)/GB)
    case size >= MB:
        return fmt.Sprintf("%.2f MB", float64(size)/MB)
    case size >= KB:
        return fmt.Sprintf("%.2f KB", float64(size)/KB)
    default:
        return fmt.Sprintf("%d B", size)
    }
}

文件 IO 检查清单 ​

[ ] 使用 defer 关闭文件
[ ] 检查所有 IO 错误
[ ] 大文件使用 bufio 缓冲读取
[ ] 使用 io.Copy 代替手动复制
[ ] 路径使用 filepath 包处理 (跨平台)
[ ] 临时文件使用后清理
[ ] 目录遍历处理错误情况
[ ] 文件权限设置合理
[ ] 使用 ReadFile/WriteFile 简化小文件操作
[ ] 大量小文件考虑使用 tar/zip 打包
[ ] 敏感文件设置合适的权限
[ ] 文件锁避免并发问题