引入
Go os 包 打开了文件,但怎么从文件里读数据、怎么把数据写到文件?Go 的答案是 io 包。io 包定义了两个最核心的接口——Reader 和 Writer,几乎所有 Go 的输入输出操作都围绕它们展开。理解了 io,就理解了 Go 处理数据流的方式。
正文
定义
定义
io包定义了Reader和Writer接口,是 Go 流式数据处理的基石。Reader负责从数据源读取字节,Writer负责把字节写入目标。所有能读写数据的类型(文件、网络连接、内存缓冲区)都实现了这两个接口。
语法
import "io"
// Reader 接口
type Reader interface {
Read(p []byte) (n int, err error)
}
// Writer 接口
type Writer interface {
Write(p []byte) (n int, err error)
}
// 通用函数
io.Copy(dst, src) // 从 src 复制到 dst
io.ReadAll(r) // 读取全部内容例子
io.Reader
import (
"fmt"
"strings"
)
r := strings.NewReader("hello world")
buf := make([]byte, 5)
n, err := r.Read(buf)
fmt.Println(n, string(buf[:n]), err) // 5 hello <nil>
n, err = r.Read(buf)
fmt.Println(n, string(buf[:n]), err) // 5 worl <nil>
n, err = r.Read(buf)
fmt.Println(n, string(buf[:n]), err) // 1 d EOFRead 把数据读入传入的字节切片,返回实际读取的字节数和 error。读完所有数据后返回 io.EOF 表示结束。
io.Writer
import (
"fmt"
"os"
)
// os.Stdout 实现了 io.Writer
n, err := os.Stdout.Write([]byte("hello\n"))
fmt.Println(n, err) // 6 <nil>Write 把字节切片写入目标,返回实际写入的字节数和 error。os.Stdout、os.File、bytes.Buffer 都实现了 Writer。
io.Copy
import (
"io"
"os"
"strings"
)
src := strings.NewReader("hello world")
// 从 src 复制到 dst(os.Stdout)
n, err := io.Copy(os.Stdout, src)
// 输出: hello world
fmt.Println("\n复制了", n, "字节")io.Copy 自动从 Reader 读数据写入 Writer,不需要手动循环。内部会分块读取,内存效率高,适合处理大文件。
io.ReadAll
import (
"fmt"
"io"
"strings"
)
r := strings.NewReader("hello world")
data, err := io.ReadAll(r)
if err != nil {
fmt.Println("读取失败:", err)
return
}
fmt.Println(string(data)) // hello worldio.ReadAll 一次性读取 Reader 的全部内容,返回字节切片。适合数据量不大的场景,大文件用 io.Copy 更好。
bytes.Buffer
import (
"bytes"
"fmt"
)
var buf bytes.Buffer
// 写入
buf.WriteString("hello")
buf.WriteString(" ")
buf.Write([]byte("world"))
// 读取
fmt.Println(buf.String()) // hello world
fmt.Println(buf.Len()) // 11
// 重置
buf.Reset()
fmt.Println(buf.Len()) // 0bytes.Buffer 是一个可读可写的内存缓冲区,同时实现了 io.Reader 和 io.Writer。既当数据源又当目标,做中间缓冲非常方便。
实际场景:复制文件
import (
"io"
"os"
)
func copyFile(src, dst string) error {
inFile, err := os.Open(src)
if err != nil {
return err
}
defer inFile.Close()
outFile, err := os.Create(dst)
if err != nil {
return err
}
defer outFile.Close()
_, err = io.Copy(outFile, inFile)
return err
}io.Copy 配合 os.Open / os.Create 是复制文件的标准写法,几行代码搞定,而且内存效率高。
io.LimitReader
import (
"fmt"
"io"
"strings"
)
r := strings.NewReader("hello world, this is a long text")
// 只读前 11 个字节
lr := io.LimitReader(r, 11)
data, _ := io.ReadAll(lr)
fmt.Println(string(data)) // hello worldio.LimitReader 包装一个 Reader,限制最多读取的字节数。处理不可信数据源或只需要部分内容时很有用。
常见写法
| 写法 | 说明 |
|---|---|
io.Copy(dst, src) | 从 Reader 复制到 Writer |
io.ReadAll(r) | 读取 Reader 的全部内容 |
io.LimitReader(r, n) | 限制最多读 n 字节 |
io.TeeReader(r, w) | 读取时同时写入 Writer |
io.MultiReader(r1, r2) | 合并多个 Reader |
io.MultiWriter(w1, w2) | 写入时同时写多个 Writer |
bytes.Buffer | 内存缓冲区,同时实现 Reader 和 Writer |
bytes.NewReader(b) | 从字节切片创建 Reader |
特点
Reader和Writer是 Go 最核心的接口,几乎所有 IO 操作都围绕它们io.Copy内部分块读取,内存效率高,处理大文件首选io.ReadAll一次性读完,适合数据量小的场景,大文件小心内存溢出bytes.Buffer同时实现Reader和Writer,做中间缓冲最方便io.EOF是正常结束信号,不是真正的错误,读到它就说明没有更多数据了- Go 的接口设计让不同数据源(文件、网络、内存)能统一处理
- 组合函数(
LimitReader、TeeReader、MultiWriter)能灵活组合数据流
理解
io 包的精髓就两个字:抽象。Reader 和 Writer 把”从哪里读”和”写到哪里去”抽象成了统一接口,不管数据来自文件、网络还是内存,用法都一样。io.Copy 一个函数就能在任意 Reader 和 Writer 之间搬运数据,这就是接口设计的威力。bytes.Buffer 是最常用的内存实现,做缓冲、做拼接、做临时存储都好使。
引出
io.Reader 和 io.Writer 在网络编程中用得最为频繁——HTTP 请求的 body 就是 io.Reader,响应写入器就是 io.Writer。接下来看 http 包,学习怎么用 Go 构建 HTTP 服务。