io 接口体系
问题
Go 的 io.Reader 和 io.Writer 接口为什么这么重要?有哪些常用实现?
答案
核心接口
io.Reader 和 io.Writer 是 Go 标准库中最重要的两个接口:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
几乎所有 IO 操作都围绕这两个接口构建——文件、网络连接、HTTP Body、压缩、加密等都实现了它们。
常见实现
| 类型 | Reader | Writer | 说明 |
|---|---|---|---|
*os.File | ✅ | ✅ | 文件 |
*bytes.Buffer | ✅ | ✅ | 内存缓冲 |
*bytes.Reader | ✅ | ❌ | 只读字节流 |
*strings.Reader | ✅ | ❌ | 只读字符串 |
*strings.Builder | ❌ | ✅ | 高性能拼接 |
net.Conn | ✅ | ✅ | 网络连接 |
http.Request.Body | ✅ | ❌ | HTTP 请求体 |
http.ResponseWriter | ❌ | ✅ | HTTP 响应 |
*bufio.Reader | ✅ | ❌ | 带缓冲读 |
*bufio.Writer | ❌ | ✅ | 带缓冲写 |
*gzip.Reader | ✅ | ❌ | gzip 解压 |
*gzip.Writer | ❌ | ✅ | gzip 压缩 |
常用工具函数
// 复制:从 Reader 复制到 Writer
n, err := io.Copy(dst, src)
// 全部读取
data, err := io.ReadAll(reader)
// 限制读取大小
limited := io.LimitReader(reader, 1024*1024) // 最多读 1MB
// 多个 Reader 串联
multi := io.MultiReader(reader1, reader2, reader3)
// 同时写入多个 Writer
multi := io.MultiWriter(writer1, writer2)
// 分流:读取的同时写入另一个 Writer
tee := io.TeeReader(reader, writer)
// Pipe:连接 Reader 和 Writer
pr, pw := io.Pipe()
go func() {
pw.Write(data)
pw.Close()
}()
io.ReadAll(pr)
组合接口
type ReadWriter interface {
Reader
Writer
}
type ReadCloser interface {
Reader
Closer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
type ReadSeeker interface {
Reader
Seeker
}
实际应用模式
链式处理(装饰器模式)
// 文件 → gzip 解压 → 读取
file, _ := os.Open("data.gz")
defer file.Close()
gzReader, _ := gzip.NewReader(file) // 装饰: 添加解压能力
defer gzReader.Close()
bufReader := bufio.NewReader(gzReader) // 装饰: 添加缓冲能力
data, _ := io.ReadAll(bufReader)
常见面试问题
Q1: io.Reader 的 Read 方法返回值怎么理解?
答案:
Read(p []byte) (n int, err error)
n:实际读取的字节数(0 <= n <= len(p))err:错误信息。io.EOF表示读完了,不是错误- n 和 err 可以同时有值:最后一次 Read 可能返回
n > 0, err = io.EOF
正确的读取循环:
buf := make([]byte, 1024)
for {
n, err := reader.Read(buf)
if n > 0 {
process(buf[:n]) // 先处理数据
}
if err != nil {
if err == io.EOF {
break // 正常结束
}
return err // 真正的错误
}
}
Q2: io.Copy 和 io.ReadAll 有什么区别?
答案:
io.Copy(dst, src):流式复制,不需要把全部数据加载到内存,适合大文件io.ReadAll(reader):一次性读取全部内容到[]byte,适合小数据
大文件必须用 io.Copy,否则可能 OOM。
Q3: 为什么 Go 的 io 接口这么成功?
答案:
- 极简:只有一个方法(Read 或 Write),任何类型都容易实现
- 组合性:通过装饰器模式自由组合(缓冲 + 压缩 + 加密)
- 通用性:文件、网络、内存、压缩、加密全部统一接口
- 零耦合:函数只依赖接口,不依赖具体类型