文件操作是编程中最基础也最常用的需求。Go 语言的 io 包设计优雅,通过 io.Reader 和 io.Writer 接口实现了高度的抽象和复用。
1. 文件读取
1.1 一次性读取整个文件
package main
import (
"fmt"
"os"
)
func main() {
// 方法一:os.ReadFile (推荐,Go 1.16+)
data, err := os.ReadFile("test.txt")
if err != nil {
panic(err)
}
fmt.Println(string(data))
// 方法二:ioutil.ReadFile (已废弃,但仍可用)
// data, err := ioutil.ReadFile("test.txt")
}
1.2 逐行读取(大文件)
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Open("large.txt")
if err != nil {
panic(err)
}
defer file.Close() // 确保文件关闭
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
panic(err)
}
}
1.3 按块读取
func main() {
file, _ := os.Open("data.bin")
defer file.Close()
buffer := make([]byte, 1024) // 每次读 1KB
for {
n, err := file.Read(buffer)
if err == io.EOF {
break // 文件读完
}
if err != nil {
panic(err)
}
fmt.Printf("Read %d bytes\n", n)
// 处理 buffer[:n]
}
}
2. 文件写入
2.1 一次性写入
func main() {
data := []byte("Hello, World!\n")
// 写入文件(会覆盖原文件)
// 0644 是文件权限:rw-r--r--
err := os.WriteFile("output.txt", data, 0644)
if err != nil {
panic(err)
}
}
2.2 追加写入
func main() {
file, err := os.OpenFile("log.txt",
os.O_APPEND|os.O_CREATE|os.O_WRONLY, // 追加模式
0644)
if err != nil {
panic(err)
}
defer file.Close()
file.WriteString("New log entry\n")
}
2.3 使用 bufio 缓冲写入
func main() {
file, _ := os.Create("output.txt")
defer file.Close()
writer := bufio.NewWriter(file)
for i := 0; i < 1000; i++ {
writer.WriteString(fmt.Sprintf("Line %d\n", i))
}
writer.Flush() // 重要:将缓冲区内容写入文件
}
3. 文件与目录操作
3.1 检查文件是否存在
func fileExists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
3.2 创建目录
// 创建单层目录
os.Mkdir("mydir", 0755)
// 创建多层目录(类似 mkdir -p)
os.MkdirAll("path/to/mydir", 0755)
3.3 遍历目录
import (
"fmt"
"os"
"path/filepath"
)
func main() {
// 方法一:filepath.Walk
filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
fmt.Println("File:", path)
}
return nil
})
// 方法二:os.ReadDir (Go 1.16+)
entries, _ := os.ReadDir(".")
for _, entry := range entries {
fmt.Println(entry.Name(), entry.IsDir())
}
}
3.4 删除文件/目录
// 删除文件
os.Remove("file.txt")
// 删除目录及其所有内容(类似 rm -rf)
os.RemoveAll("mydir")
4. io.Copy 文件拷贝
4.1 复制文件
func copyFile(src, dst string) error {
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()
// io.Copy 高效复制
_, err = io.Copy(destination, source)
return err
}
4.2 显示进度的复制
type ProgressReader struct {
reader io.Reader
total int64
read int64
}
func (pr *ProgressReader) Read(p []byte) (int, error) {
n, err := pr.reader.Read(p)
pr.read += int64(n)
// 打印进度
fmt.Printf("\rProgress: %.2f%%", float64(pr.read)/float64(pr.total)*100)
return n, err
}
5. 网络文件下载
import (
"io"
"net/http"
"os"
)
func downloadFile(url, filepath string) error {
// 发起 HTTP GET 请求
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// 创建文件
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
// 将响应体复制到文件
_, err = io.Copy(out, resp.Body)
return err
}
func main() {
downloadFile("https://example.com/file.zip", "file.zip")
}
6. io.Reader 和 io.Writer 接口
6.1 理解接口
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
实现了这两个接口的类型:
os.Filebytes.Bufferstrings.Readernet.Connhttp.Response.Body
6.2 组合使用
import (
"bytes"
"io"
"strings"
)
func main() {
// strings.Reader -> bytes.Buffer
reader := strings.NewReader("Hello, World!")
var buffer bytes.Buffer
io.Copy(&buffer, reader)
fmt.Println(buffer.String()) // Hello, World!
}
7. 临时文件与目录
import (
"os"
)
func main() {
// 创建临时文件
tmpFile, err := os.CreateTemp("", "example-*.txt")
if err != nil {
panic(err)
}
defer os.Remove(tmpFile.Name()) // 清理
tmpFile.WriteString("Temporary data")
tmpFile.Close()
// 创建临时目录
tmpDir, _ := os.MkdirTemp("", "example-")
defer os.RemoveAll(tmpDir)
}
8. 总结
- 读取:小文件用
os.ReadFile,大文件用bufio.Scanner - 写入:小文件用
os.WriteFile,大量写入用bufio.Writer - 复制:使用
io.Copy,高效且简洁 - 接口:理解
io.Reader和io.Writer,它们是 Go IO 的核心
思考题:
为什么 bufio.Writer 需要手动调用 Flush()?如果忘记调用会发生什么?