引入

Go os 包 打开了文件,但怎么从文件里读数据、怎么把数据写到文件?Go 的答案是 io 包。io 包定义了两个最核心的接口——ReaderWriter,几乎所有 Go 的输入输出操作都围绕它们展开。理解了 io,就理解了 Go 处理数据流的方式。

正文

定义

定义

io 包定义了 ReaderWriter 接口,是 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 EOF

Read 把数据读入传入的字节切片,返回实际读取的字节数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 把字节切片写入目标,返回实际写入的字节数erroros.Stdoutos.Filebytes.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 world

io.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())  // 0

bytes.Buffer 是一个可读可写的内存缓冲区,同时实现了 io.Readerio.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 world

io.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内存缓冲区,同时实现 ReaderWriter
bytes.NewReader(b)从字节切片创建 Reader

特点

  • ReaderWriter 是 Go 最核心的接口,几乎所有 IO 操作都围绕它们
  • io.Copy 内部分块读取,内存效率高,处理大文件首选
  • io.ReadAll 一次性读完,适合数据量小的场景,大文件小心内存溢出
  • bytes.Buffer 同时实现 ReaderWriter,做中间缓冲最方便
  • io.EOF 是正常结束信号,不是真正的错误,读到它就说明没有更多数据了
  • Go 的接口设计让不同数据源(文件、网络、内存)能统一处理
  • 组合函数(LimitReaderTeeReaderMultiWriter)能灵活组合数据流

理解

io 包的精髓就两个字:抽象ReaderWriter 把”从哪里读”和”写到哪里去”抽象成了统一接口,不管数据来自文件、网络还是内存,用法都一样。io.Copy 一个函数就能在任意 ReaderWriter 之间搬运数据,这就是接口设计的威力。bytes.Buffer 是最常用的内存实现,做缓冲、做拼接、做临时存储都好使。

引出

io.Readerio.Writer 在网络编程中用得最为频繁——HTTP 请求的 body 就是 io.Reader,响应写入器就是 io.Writer。接下来看 http 包,学习怎么用 Go 构建 HTTP 服务。