Go 标准库学习:io


二月其一,学习 Go 的 io 包和 Buffer 结构体(Go 版本 1.16.5)。

io 包定义了 Go 的 IO 接口,Buffer 结构体实现了其中大部分接口。明确一下代码位置:io 包下的 io.go 文件,以及 bytes 包下的 buffer.go 文件。

学习过程中,主要参考博文《Golang学习 - io 包》,写得很好。


io 包

io 包定义了很多 interface,每个 interface 定义了一个基本的方法,没有实现,例如:

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

Reader 这样的 interface 有很多,类似的还有 WriterReaderFromWriterTo 等等。

io 包由这样一个个的 interface 组成,这些 interface 定义了 Go 语言中的 IO 标准(只是单纯地定义),本身没有实现,具体怎么实现,交给别的包。

Package io provides basic interfaces to I/O primitives. Its primary job is to wrap existing implementations of such primitives, such as those in package os, into shared public interfaces that abstract the functionality, plus some other related primitives.


Because these interfaces and primitives wrap lower-level operations with various implementations, unless otherwise informed clients should not assume they are safe for parallel execution.


————————

io 包提供了 I/O 原语的基本接口。它的首要任务是提供 io 原语的包装实现,例如把 os 包中的 io 原语,转换成公共抽象接口,并增加一些相关原语。

因为这些接口和原语包装的都是原始操作,且实现众多,因此除非有声明,否则使用时不应假设它们并发安全。


Buffer 结构体

buffer.go 文件的注释非常简单:

Simple byte buffer for marshaling data.


————————

用于整理(marshal)数据的简单字节缓冲区。

Buffer 结构体(struct)实现了很多 io 基本接口,而且实现简单,因此我们通过 Buffer 结构体来学习 io 包。

比如 io 包定义了 Reader 接口及其内部的 Read 方法,而 Buffer 就实现了 Read 方法:

1
2
3
buffer := bytes.NewBufferString("abc")
p := make([]byte, 3)
buffer.Read(p) // 此时切片p:['a', 'b', 'c']

io API

下表整理了一下,io 包(准确的说是 io.go 文件中)都定义了哪些 interface 和方法,以及 Buffer 结构体实现了哪些部分。

io interface 嵌套 interface func 功能 buffer 是否实现
Reader Read(p []byte) (n int, err error) 用于输出自身的数据(-> p)
Writer Write(p []byte) (n int, err error) 用于将数据存入自身(p ->)
Closer Close() error 用于关闭数据读写(关闭文件、通道、连接、数据库……)
Seeker Seek(offset int64, whence int) (int64, error) 用于移动数据的读写指针(每一次读写操作都从指针位置开始)
ReadWriter Reader
Writer
组合接口
ReadCloser Reader
Closer
组合接口
WriteCloser Writer
Closer
组合接口
ReadWriteCloser Reader
Writer
Closer
组合接口
ReadSeeker Reader
Seeker
组合接口
ReadSeekCloser Reader
Seeker
Closer
组合接口
WriteSeeker Writer
Seeker
组合接口
ReadWriteSeeker Reader
Writer
Seeker
组合接口
ReaderFrom ReadFrom(r Reader) (n int64, err error) 用于从 r 中读取数据存入自身
WriterTo WriteTo(w Writer) (n int64, err error) 用于将自身的数据写入 w 中
ReaderAt ReadAt(p []byte, off int64) (n int, err error) 用于从指定偏移位置开始,输出自身的数据(-> p)
WriterAt WriteAt(p []byte, off int64) (n int, err error) 用于从指定偏移位置开始,将数据存入自身(p ->)
ByteReader ReadByte() (byte, error) 用于从自身读出一个字节
ByteScanner ByteReader UnreadByte() error 用于从自身读出一个字节,且可以撤销最后一次读取(下次可以读出一样的数据)
ByteWriter WriteByte(c byte) error 用于将一个字节写入自身
RuneReader ReadRune() (r rune, size int, err error) 用于从自身读取一个 UTF-8 编码的字符到 r 中
RuneScanner RuneReader UnreadRune() error 用于从自身读取一个 UTF-8 编码的字符到 r 中,且可以撤销最后一次读取(下次可以读出一样的数据)
StringWriter WriteString(s string) (n int, err error) 用于将字符串 s 写入到 w 中

可以看出,Buffer 实现了其中大部分的接口。

下面列出 Buffer 实现的全部 io 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
buffer := bytes.NewBuffer([]byte{}) // 下文注释中,buffer实例内部的存储内存,用【自身🗂️】表示


// 数组 p1 -> 自身🗂️
p1 := []byte("abc")
writeNum, err := buffer.Write(p1)
fmt.Printf("【written bytes num: %d】,【buffer: %s】,【err: %s】", writeNum, buffer.String(), err)
// 打印:【written bytes num: 3】,【buffer: abc】,【err: %!s(<nil>)】


// 自身🗂️ -> 数组 p2
p2 := []byte("wxyz")
readNum, err := buffer.Read(p2)
fmt.Printf("【read bytes num: %d】,【p2: %s】,【buffer: %s】,【err: %s】", readNum, p2, buffer.String(), err)
// 打印:【read bytes num: 3】,【p2: abcz】,【buffer: 】,【err: %!s(<nil>)】


// Reader r -> 自身🗂️
r := bytes.NewBuffer([]byte("long long long long buffer"))
readFromNum, err := buffer.ReadFrom(r)
fmt.Printf("【read from bytes num: %d】,【buffer: %s】,【r: %s】,【err: %s】", readFromNum, buffer.String(), r.String(), err)
// 打印:【read from bytes num: 26】,【buffer: long long long long buffer】,【r: 】,【err: %!s(<nil>)】


// 自身🗂️ -> Writer w
w := bytes.NewBuffer([]byte{})
writeToNum, err := buffer.WriteTo(w)
fmt.Printf("【write to bytes num: %d】,【buffer: %s】,【w: %s】,【err: %s】", writeToNum, buffer.String(), w.String(), err)
// 打印:【write to bytes num: 26】,【buffer: 】,【w: long long long long buffer】,【err: %!s(<nil>)】


// 自身🗂️ -> 1 byte
buffer.Write([]byte("hello"))
for buffer.Len() > 0 {
readByte, err := buffer.ReadByte()
fmt.Printf("【read byte: %c】,【err: %s】\n", readByte, err)
// 打印:
//【read byte: h】,【err: %!s(<nil>)】
//【read byte: e】,【err: %!s(<nil>)】
//【read byte: l】,【err: %!s(<nil>)】
//【read byte: l】,【err: %!s(<nil>)】
//【read byte: o】,【err: %!s(<nil>)】
}


// 1 byte -> 自身🗂️
for i := 0; i < 5; i++ {
buffer.WriteByte('a')
}
fmt.Printf("【buffer: %s】", buffer.String())
// 打印:【buffer: aaaaa】


// 自身🗂️ -> 1 rune
buffer.Reset()
buffer.Write([]byte("你好呀"))
readRune, size, err := buffer.ReadRune()
fmt.Printf("【read rune: %c】,【size: %d】,【buffer: %s】,【err: %s】", readRune, size, buffer.String(), err)
// 打印:【read rune: 你】,【size: 3】,【buffer: 好呀】,【err: %!s(<nil>)】


// 字符串 -> 自身🗂️
buffer.Reset()
writeStringNum, err := buffer.WriteString("hello")
fmt.Printf("【write string num: %d】,【buffer: %s】,【err: %s】", writeStringNum, buffer.String(), err)
// 打印:【write string num: 5】,【buffer: hello】,【err: %!s(<nil>)】

Buffer 实现

Buffer 结构体的设计很简单:

1
2
3
4
5
type Buffer struct {
buf []byte // contents are the bytes buf[off : len(buf)]
off int // read at &buf[off], write at &buf[len(buf)]
lastRead readOp // last read operation, so that Unread* can work correctly.
}

Buffer 结构体内部只有三个变量:

  • byte 数组,用于存放数据

  • 下次读缓存的起始下标(下一次调用 Read 方法时,读缓存的起点是 buf[off]

  • 枚举:上次使用 Read 方法,最后读到了什么(用于 Read 之后 Unread 撤回)

    有六种,分别是【从没读过】、【读了长 1 byte 的字符】、【读了长 2 byte 的字符】、【读了长 3 byte 的字符】、【读了长 4 byte 的字符】、【读了其他长度的字符】

源码就不分析了。


文章《Golang bytes.Buffer 用法精述》总结了 Buffer 的常用方法(部分跟上文重复),这里拷贝如下:

往 Buffer 中写入数据:

1
2
3
4
5
b.Write(d []byte) (n int, err error)         // 将切片d写入Buffer尾部
b.WriteString(s string) (n int, err error) // 将字符串s写入Buffer尾部
b.WriteByte(c byte) error // 将字符c写入Buffer尾部
b.WriteRune(r rune) (n int, err error) // 将一个rune类型的数据放到缓冲区的尾部
b.ReadFrom(r io.Reader) (n int64, err error) // 从实现了io.Reader接口的可读取对象写入Buffer尾部

从 Buffer 中读取数据:

1
2
3
4
5
6
7
b.Next(n int) []byte                                  // 读取 n 个字节数据并返回,如果 buffer 不足 n 字节,则读取全部
b.Read(p []byte) (n int, err error) // 一次读取 len(p) 个 byte 到 p 中,每次读取新的内容将覆盖p中原来的内容。成功返回实际读取的字节数,off 向后偏移 n,buffer 没有数据返回错误 io.EOF
b.ReadByte() (byte, error) // 读取第一个byte并返回,off 向后偏移 n
b.ReadRune() (r rune, size int, err error) // 读取第一个 UTF8 编码的字符并返回该字符和该字符的字节数,b的第1个rune被拿掉。如果buffer为空,返回错误 io.EOF,如果不是UTF8编码的字符,则消费一个字节,返回 (U+FFFD,1,nil)
b.ReadBytes(delimiter byte) (line []byte, err error) // 读取缓冲区第一个分隔符前面的内容以及分隔符并返回,缓冲区会清空读取的内容。如果没有发现分隔符,则返回读取的内容并返回错误io.EOF
b.ReadString(delimiter byte) (line string, err error) // 读取缓冲区第一个分隔符前面的内容以及分隔符并作为字符串返回,缓冲区会清空读取的内容。如果没有发现分隔符,则返回读取的内容并返回错误 io.EOF
b.WriteTo(w io.Writer) (n int64, err error) // 将 Buffer 中的内容输出到实现了 io.Writer 接口的可写入对象中,成功返回写入的字节数,失败返回错误

其他操作:

1
2
3
4
5
6
7
8
9
b.Bytes() []byte     // 返回字节切片
b.Cap() int // 返回 buffer 内部字节切片的容量
b.Grow(n int) // 为 buffer 内部字节切片的容量增加 n 字节
b.Len() int // 返回缓冲区数据长度,等于 len(b.Bytes())
b.Reset() // 清空数据
b.String() string // 字符串化
b.Truncate(n int) // 丢弃缓冲区中除前n个未读字节以外的所有字节。如果 n 为负数或大于缓冲区长度,则引发 panic
b.UnreadByte() error // 将最后一次读取操作中被成功读取的字节设为未被读取的状态,即将已读取的偏移 off 减 1
b.UnreadRune() error // 将最后一次 ReadRune() 读取操作返回的 UTF8 字符 rune设为未被读取的状态,即将已读取的偏移 off 减去 字符 rune 的字节数

ioutil

还有一个 ioutil 包,用来提供 io 相关的工具方法。

在 Go 1.16 版本之后,ioutil 包的各个方法,基本都被其他包重新实现了,例如 ioutil.ReadDir 推荐替换成 os.ReadDir

ioutil 包下的方法整理如下:

ioutil 方法 功能 Go 1.16 之后推荐替代方法
ReadAll(r io.Reader) ([]byte, error) 从 io.Reader 中读取全部数据,返回 []byte io.ReadAll
ReadDir(dirname string) ([]fs.FileInfo, error) 读取目录下的所有文件(向下一级) os.ReadDir
ReadFile(filename string) ([]byte, error) 读取文件,返回 []byte os.ReadFile
WriteFile(filename string, data []byte, perm fs.FileMode) error 把 []byte 写到文件 os.WriteFile
NopCloser(r io.Reader) io.ReadCloser 把 io.Reader 包装成 io.ReadCloser io.NopCloser
TempDir(dir, pattern string) (name string, err error) 创建临时目录
TempFile(dir, pattern string) (f *os.File, err error) 创建临时文件