文件操作是编程中最基础也最常用的需求。Go 语言的 io 包设计优雅,通过 io.Readerio.Writer 接口实现了高度的抽象和复用。

1. 文件读取

1.1 一次性读取整个文件

 1package main
 2
 3import (
 4    "fmt"
 5    "os"
 6)
 7
 8func main() {
 9    // 方法一:os.ReadFile (推荐,Go 1.16+)
10    data, err := os.ReadFile("test.txt")
11    if err != nil {
12        panic(err)
13    }
14    fmt.Println(string(data))
15
16    // 方法二:ioutil.ReadFile (已废弃,但仍可用)
17    // data, err := ioutil.ReadFile("test.txt")
18}

1.2 逐行读取(大文件)

 1import (
 2    "bufio"
 3    "fmt"
 4    "os"
 5)
 6
 7func main() {
 8    file, err := os.Open("large.txt")
 9    if err != nil {
10        panic(err)
11    }
12    defer file.Close()  // 确保文件关闭
13
14    scanner := bufio.NewScanner(file)
15    for scanner.Scan() {
16        line := scanner.Text()
17        fmt.Println(line)
18    }
19
20    if err := scanner.Err(); err != nil {
21        panic(err)
22    }
23}

1.3 按块读取

 1func main() {
 2    file, _ := os.Open("data.bin")
 3    defer file.Close()
 4
 5    buffer := make([]byte, 1024)  // 每次读 1KB
 6    for {
 7        n, err := file.Read(buffer)
 8        if err == io.EOF {
 9            break  // 文件读完
10        }
11        if err != nil {
12            panic(err)
13        }
14        fmt.Printf("Read %d bytes\n", n)
15        // 处理 buffer[:n]
16    }
17}

2. 文件写入

2.1 一次性写入

 1func main() {
 2    data := []byte("Hello, World!\n")
 3
 4    // 写入文件(会覆盖原文件)
 5    // 0644 是文件权限:rw-r--r--
 6    err := os.WriteFile("output.txt", data, 0644)
 7    if err != nil {
 8        panic(err)
 9    }
10}

2.2 追加写入

 1func main() {
 2    file, err := os.OpenFile("log.txt",
 3        os.O_APPEND|os.O_CREATE|os.O_WRONLY,  // 追加模式
 4        0644)
 5    if err != nil {
 6        panic(err)
 7    }
 8    defer file.Close()
 9
10    file.WriteString("New log entry\n")
11}

2.3 使用 bufio 缓冲写入

 1func main() {
 2    file, _ := os.Create("output.txt")
 3    defer file.Close()
 4
 5    writer := bufio.NewWriter(file)
 6
 7    for i := 0; i < 1000; i++ {
 8        writer.WriteString(fmt.Sprintf("Line %d\n", i))
 9    }
10
11    writer.Flush()  // 重要:将缓冲区内容写入文件
12}

3. 文件与目录操作

3.1 检查文件是否存在

1func fileExists(path string) bool {
2    _, err := os.Stat(path)
3    return !os.IsNotExist(err)
4}

3.2 创建目录

1// 创建单层目录
2os.Mkdir("mydir", 0755)
3
4// 创建多层目录(类似 mkdir -p)
5os.MkdirAll("path/to/mydir", 0755)

3.3 遍历目录

 1import (
 2    "fmt"
 3    "os"
 4    "path/filepath"
 5)
 6
 7func main() {
 8    // 方法一:filepath.Walk
 9    filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
10        if err != nil {
11            return err
12        }
13        if !info.IsDir() {
14            fmt.Println("File:", path)
15        }
16        return nil
17    })
18
19    // 方法二:os.ReadDir (Go 1.16+)
20    entries, _ := os.ReadDir(".")
21    for _, entry := range entries {
22        fmt.Println(entry.Name(), entry.IsDir())
23    }
24}

3.4 删除文件/目录

1// 删除文件
2os.Remove("file.txt")
3
4// 删除目录及其所有内容(类似 rm -rf)
5os.RemoveAll("mydir")

4. io.Copy 文件拷贝

4.1 复制文件

 1func copyFile(src, dst string) error {
 2    source, err := os.Open(src)
 3    if err != nil {
 4        return err
 5    }
 6    defer source.Close()
 7
 8    destination, err := os.Create(dst)
 9    if err != nil {
10        return err
11    }
12    defer destination.Close()
13
14    // io.Copy 高效复制
15    _, err = io.Copy(destination, source)
16    return err
17}

4.2 显示进度的复制

 1type ProgressReader struct {
 2    reader io.Reader
 3    total  int64
 4    read   int64
 5}
 6
 7func (pr *ProgressReader) Read(p []byte) (int, error) {
 8    n, err := pr.reader.Read(p)
 9    pr.read += int64(n)
10
11    // 打印进度
12    fmt.Printf("\rProgress: %.2f%%", float64(pr.read)/float64(pr.total)*100)
13
14    return n, err
15}

5. 网络文件下载

 1import (
 2    "io"
 3    "net/http"
 4    "os"
 5)
 6
 7func downloadFile(url, filepath string) error {
 8    // 发起 HTTP GET 请求
 9    resp, err := http.Get(url)
10    if err != nil {
11        return err
12    }
13    defer resp.Body.Close()
14
15    // 创建文件
16    out, err := os.Create(filepath)
17    if err != nil {
18        return err
19    }
20    defer out.Close()
21
22    // 将响应体复制到文件
23    _, err = io.Copy(out, resp.Body)
24    return err
25}
26
27func main() {
28    downloadFile("https://example.com/file.zip", "file.zip")
29}

6. io.Reader 和 io.Writer 接口

6.1 理解接口

1type Reader interface {
2    Read(p []byte) (n int, err error)
3}
4
5type Writer interface {
6    Write(p []byte) (n int, err error)
7}

实现了这两个接口的类型:

  • os.File
  • bytes.Buffer
  • strings.Reader
  • net.Conn
  • http.Response.Body

6.2 组合使用

 1import (
 2    "bytes"
 3    "io"
 4    "strings"
 5)
 6
 7func main() {
 8    // strings.Reader -> bytes.Buffer
 9    reader := strings.NewReader("Hello, World!")
10    var buffer bytes.Buffer
11
12    io.Copy(&buffer, reader)
13
14    fmt.Println(buffer.String())  // Hello, World!
15}

7. 临时文件与目录

 1import (
 2    "os"
 3)
 4
 5func main() {
 6    // 创建临时文件
 7    tmpFile, err := os.CreateTemp("", "example-*.txt")
 8    if err != nil {
 9        panic(err)
10    }
11    defer os.Remove(tmpFile.Name())  // 清理
12
13    tmpFile.WriteString("Temporary data")
14    tmpFile.Close()
15
16    // 创建临时目录
17    tmpDir, _ := os.MkdirTemp("", "example-")
18    defer os.RemoveAll(tmpDir)
19}

8. 总结

  • 读取:小文件用 os.ReadFile,大文件用 bufio.Scanner
  • 写入:小文件用 os.WriteFile,大量写入用 bufio.Writer
  • 复制:使用 io.Copy,高效且简洁
  • 接口:理解 io.Readerio.Writer,它们是 Go IO 的核心

思考题: 为什么 bufio.Writer 需要手动调用 Flush()?如果忘记调用会发生什么?


相关阅读