Go io.Reader 与 io.Writer

Go 中最核心的两个 I/O 接口,通过统一的 Read/Write 契约将文件、网络、内存等所有数据源抽象为可组合的流。

#type / concept #status / growing #tech / dev #resource / go

[!info] related notes

Go io.Reader 与 io.Writer

一句话定义

io.Readerio.Writer 是 Go I/O 体系的基石接口,任何实现了 Read([]byte) (int, error)Write([]byte) (int, error) 的类型都能参与统一的数据流操作。

核心机制 / 工作原理

io.Reader 接口:

type Reader interface {
    Read(p []byte) (n int, err error)
}

契约:将数据读入 p,返回实际读取的字节数 n。当数据读完时返回 io.EOFn 可以小于 len(p),这是合法的(短读取)。

io.Writer 接口:

type Writer interface {
    Write(p []byte) (n int, err error)
}

契约:将 p 中的数据写出,返回实际写入的字节数 nn < len(p) 时必须返回非 nil error。

实现这两个接口的常见类型:

类型ReaderWriter
*os.FileYesYes
*net.ConnYesYes
*bytes.BufferYesYes
*strings.ReaderYesNo
*http.Response.BodyYesNo

可组合的装饰器(Decorator Pattern):

  • bufio.NewReader/Writer — 加缓冲层,减少系统调用
  • io.TeeReader(r, w) — 读取的同时将数据写入另一处(如计算哈希)
  • io.MultiReader(r1, r2, ...) — 串联多个 Reader
  • io.LimitReader(r, n) — 最多读取 n 字节
  • io.MultiWriter(w1, w2, ...) — 同时写入多个 Writer

标准工具函数:

// 将 src 的全部内容复制到 dst
io.Copy(dst Writer, src Reader) (int64, error)

// 读取 r 的全部内容
io.ReadAll(r Reader) ([]byte, error)

最小例子 / 最小场景

// 从 HTTP 响应体读取数据并写入文件,同时计算 SHA256
func downloadAndHash(url, filepath string) (string, error) {
    resp, err := http.Get(url)
    if err != nil {
        return "", err
    }
    defer resp.Body.Body.Close()

    f, err := os.Create(filepath)
    if err != nil {
        return "", err
    }
    defer f.Close()

    h := sha256.New()
    // TeeReader: 读取的同时将数据写入 h
    reader := io.TeeReader(resp.Body, h)
    // MultiWriter: 同时写入文件
    _, err = io.Copy(f, reader)
    if err != nil {
        return "", err
    }

    return hex.EncodeToString(h.Sum(nil)), nil
}

这里 io.TeeReader + io.Copy 的组合实现了”读一遍、写文件、算哈希”三个操作,而不需要自己管理缓冲区和循环。

为什么重要

  • 统一抽象:函数签名只依赖 io.Reader / io.Writer,不关心底层是文件、网络还是内存,极大提升了代码复用性。
  • 可组合性:装饰器可以像积木一样叠加(缓冲 → 限制大小 → 计算哈希),每个装饰器只做一件事。
  • 可测试性:测试时用 bytes.Bufferstrings.Reader 替代真实 I/O,无需文件系统或网络。
  • 零拷贝理念:数据从 Reader 流向 Writer,不需要先全部读入内存再写出,适合处理大文件。

边界与易混淆点

  • io.EOF 不是错误,而是信号——表示数据已读完。只有当 n == 0err == io.EOF 时才应终止循环;短读取时可能 n > 0err == io.EOF
  • Read 返回 n < len(p) 是完全合法的,不要假设一次 Read 能填满整个切片。
  • io.Copy 内部会处理短读取和短写入,通常优先使用它而非手动循环。
  • *http.Response.Body 必须关闭(defer resp.Body.Close()),否则底层 TCP 连接不会被复用。
  • bytes.Buffer 既是 Reader 又是 Writer,但它不是并发安全的。
创建于 2026/6/25 更新于 2026/6/25