跳到主要内容

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、压缩、加密等都实现了它们。

常见实现​

类型ReaderWriter说明
*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 接口这么成功?​

答案:

  1. 极简:只有一个方法(Read 或 Write),任何类型都容易实现
  2. 组合性:通过装饰器模式自由组合(缓冲 + 压缩 + 加密)
  3. 通用性:文件、网络、内存、压缩、加密全部统一接口
  4. 零耦合:函数只依赖接口,不依赖具体类型

相关链接​