文件操作是编程中最基础也最常用的需求。Go 语言的 io 包设计优雅,通过 io.Reader 和 io.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.Filebytes.Bufferstrings.Readernet.Connhttp.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.Reader和io.Writer,它们是 Go IO 的核心
思考题:
为什么 bufio.Writer 需要手动调用 Flush()?如果忘记调用会发生什么?