一、bufio包的作用
bufio
包是 Go 语言标准库中提供的一个缓冲 I/O 功能的工具包。它主要用于提供对 I/O 操作的缓冲支持,可以帮助提升 I/O 操作的性能和效率。
bufio
包提供了一组功能强大的工具,使得对数据的读取和写入变得更加高效、灵活,并且可以减少对系统资源的频繁占用,特别适用于处理大量数据或需要高性能的 I/O 操作场景。
具体来说,bufio
包提供了如下功能和作用:
-
缓冲读取(Buffered Reading):
bufio
包通过Reader
类型提供了缓冲读取的功能,可以减少对底层数据源(比如文件、网络连接等)的实际读取次数,从而提高读取性能。
-
缓冲写入(Buffered Writing):
- 通过
Writer
类型,bufio
包支持缓冲写入,将数据暂时存储在内存中,减少对底层数据目标的实际写入次数,提高写入性能。
- 通过
-
高效的字节处理(Efficient Byte Handling):
- 提供了对字节的高效处理方法,比如按字节读取和写入,以及其他基于字节的操作,这对于处理二进制数据非常有用。
-
行扫描(Line Scanning):
bufio.Scanner
类型可以方便地对文本进行分割,支持按行扫描,用户可以定义自己的分割函数,方便地处理各种文本格式。
-
灵活性和性能提升:
- 通过在内存中缓冲数据,减少了系统调用的次数,从而提高了 I/O 操作的效率和性能。
二、bufio.go
1、bufio 包的目的和基本结构
// Package bufio 实现了带缓冲的 I/O 操作。它封装了一个 io.Reader 或 io.Writer
// 对象,创建了另一个实现相同接口的对象(Reader 或 Writer),但提供了缓冲和
// 一些文本 I/O 的辅助功能。
package bufio
import (
"bytes"
"errors"
"io"
"strings"
"unicode/utf8"
)
// 默认缓冲区大小
const (
defaultBufSize = 4096
)
var (
// ErrInvalidUnreadByte 表示对 UnreadByte 的无效使用
ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
// ErrInvalidUnreadRune 表示对 UnreadRune 的无效使用
ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
// ErrBufferFull 表示缓冲区已满
ErrBufferFull = errors.New("bufio: buffer full")
// ErrNegativeCount 表示计数为负值
ErrNegativeCount = errors.New("bufio: negative count")
)
// 缓冲输入。
// Reader 实现了对 io.Reader 对象的缓冲。它包装了一个由客户端提供的 io.Reader,
// 创建了另一个对象(Reader),该对象同样实现了接口,但提供了缓冲和一些文本 I/O 的帮助。
type Reader struct {
buf []byte // 缓冲区
rd io.Reader // 客户端提供的 reader
r, w int // buf 的读写位置
err error // 错误信息
lastByte int // 上一次读取的字节,用于 UnreadByte;-1 表示无效
lastRuneSize int // 上一次读取的 rune 大小,用于 UnreadRune;-1 表示无效
}
// 最小读取缓冲区大小
const minReadBufferSize = 16
// 最大连续空读取次数
const maxConsecutiveEmptyReads = 100
// NewReaderSize 返回一个新的 [Reader],其缓冲区大小至少为指定大小。如果参数 io.Reader
// 已经是一个拥有足够大大小的 [Reader],则返回底层 [Reader]。
func NewReaderSize(rd io.Reader, size int) *Reader {
// 它已经是一个 Reader 吗?
b, ok := rd.(*Reader)
if ok && len(b.buf) >= size {
return b
}
r := new(Reader)
r.reset(make([]byte, max(size, minReadBufferSize)), rd)
return r
}
// NewReader 返回一个新的 [Reader],其缓冲区大小为默认大小。
func NewReader(rd io.Reader) *Reader {
return NewReaderSize(rd, defaultBufSize)
}
上述注释解释了 bufio
包的目的和基本结构。
更详细的解释:
bufio
包实现了带缓冲的 I/O 操作,用于提高 I/O 操作的性能和效率。Reader
结构体是对io.Reader
对象的缓冲实现。它包含一个缓冲区buf
、一个客户端提供的io.Reader
对象rd
、以及读写位置和错误信息等字段。const
声明了一些常量,如默认缓冲区大小、最小读取缓冲区大小和最大连续空读取次数。var
声明了一些错误变量,用于表示在使用Reader
过程中可能发生的错误情况。NewReaderSize
函数返回一个新的Reader
,其缓冲区大小至少为指定大小。如果参数io.Reader
已经是一个拥有足够大大小的Reader
,则返回底层的Reader
。NewReader
函数返回一个新的Reader
,其缓冲区大小为默认大小。
2、Size:返回底层缓冲区的大小
这部分代码定义了 Size
方法,该方法用于返回 Reader
结构体中底层缓冲区的大小(以字节为单位)。
// Size 返回底层缓冲区的大小(以字节为单位)。
func (b *Reader) Size() int {
return len(b.buf) }
解释:
Size
方法是Reader
结构体的方法,其接收者是b
,类型是Reader
。- 方法的主体是
return len(b.buf)
,它返回b
结构体中buf
字段的长度,即底层缓冲区的大小。
作用:
- 这个方法的作用是提供外部访问
Reader
结构体底层缓冲区大小的接口。
3、Reset:重置缓冲区状态和读取源
这部分代码定义了 Reset
方法,用于重置 Reader
结构体的状态,并切换缓冲区以从新的读取源 r
进行读取。
// Reset 丢弃任何已缓存的数据,重置所有状态,并切换缓冲区以从读取源 r 进行读取。
// 在 [Reader] 的零值上调用 Reset 会将内部缓冲区初始化为默认大小。
// 调用 b.Reset(b)(即将 [Reader] 重置为自身)不会执行任何操作。
func (b *Reader) Reset(r io.Reader) {
// 如果将读取源 r 传递给 NewReader,NewReader 将返回 r。
// 代码的不同层可能会这样做,然后稍后将 r 传递给 Reset。在这种情况下避免无限递归。
if b == r {
return
}
// 如果缓冲区为空,则将其初始化为具有默认大小的新字节切片。
if b.buf == nil {
b.buf = make([]byte, defaultBufSize)
}
// 调用 reset 方法,将缓冲区、读取源以及其他状态重置。
b.reset(b.buf, r)
}
解释:
Reset
方法是Reader
结构体的方法,其接收者是b
,类型是Reader
。- 方法的主体包含了以下几个关键步骤:
- 避免无限递归: 如果
b
和新的读取源r
相同,直接返回,以避免无限递归。 - 初始化缓冲区: 如果
buf
字段为nil
,则将其初始化为一个具有默认大小的新字节切片。 - 调用重置方法: 调用
reset
方法,将缓冲区、读取源、上一个字节和上一个符文大小等状态重置。
- 避免无限递归: 如果
// reset 方法用于具体执行 [Reader] 结构体的重置,将各个字段设置为新的状态。
func (b *Reader) reset(buf []byte, r io.Reader) {
*b = Reader{
buf: buf,
rd: r,
lastByte: -1,
lastRuneSize: -1,
}
}
这个内部的 reset
方法用于具体执行 Reader
结构体的重置,将各个字段设置为新的状态。
作用:
Reset
方法的作用是在切换读取源或者需要丢弃任何已缓存的数据时,重置Reader
结构体的状态,以便重新开始读取新的数据。- 如果
Reset
方法被调用时传入的r
与当前的b
相同,表示正在尝试将Reader
重置为自身,此时直接返回,不进行任何操作。 - 当
Reader
是零值时(未使用NewReader
初始化),调用Reset
会将内部缓冲区初始化为默认大小。
4、fill:填充缓冲区
这部分代码定义了 fill
方法,用于在缓冲区中读取新的数据块。
var errNegativeRead = errors.New("bufio: reader returned negative count from Read")
// fill 读取一个新的数据块填充到缓冲区中。
func (b *Reader) fill() {
// 将现有数据滑动到开头。
if b.r > 0 {
copy(b.buf, b.buf[b.r:b.w])
b.w -= b.r
b.r = 0
}
// 如果缓冲区已满,则抛出 panic。
if b.w >= len(b.buf) {
panic("bufio: tried to fill full buffer")
}
// 读取新数据:尝试有限次数。
for i := maxConsecutiveEmptyReads; i > 0; i-- {
// 从读取源 b.rd 中读取新的数据块到缓冲区中。
n, err := b.rd.Read(b.buf[b.w:])
// 如果读取的字节数 n 小于零,抛出 panic,表示读取返回了负数。
if n < 0 {
panic(errNegativeRead)
}
// 将写指针 b.w 向前移动,表示成功读取了 n 个字节。
b.w += n
// 如果出现错误,将错误存储在 b.err 中,并结束方法。
if err != nil {
b.err = err
return
}
// 如果成功读取了至少一个字节,退出循环。
if n > 0 {
return
}
}
// 如果整个循环完成后仍未读取任何字节,将 b.err 设置为 io.ErrNoProgress。
b.err = io.ErrNoProgress
}
解释:
fill
方法是Reader
结构体的方法,没有显式的接收者,但它使用了b
结构体的字段。- 首先,如果缓冲区的读指针
b.r
大于零,表示有未读的数据在缓冲区中,将这些数据移动到缓冲区的开头。 - 然后,检查缓冲区是否已满,如果是,抛出 panic。
- 接着,通过循环一定次数(
maxConsecutiveEmptyReads
),尝试从读取源b.rd
中读取新的数据块到缓冲区中。- 如果读取的字节数
n
小于零,抛出 panic,表示读取返回了负数。 - 将写指针
b.w
向前移动,表示成功读取了n
个字节。 - 如果出现错误,将错误存储在
b.err
中,并结束方法。 - 如果成功读取了至少一个字节,退出循环。
- 如果读取的字节数
- 如果整个循环完成后仍未读取任何字节,将
b.err
设置为io.ErrNoProgress
。
作用:
fill
方法的主要作用是确保缓冲区中始终存在可读取的数据,以提高读取效率。- 它会从读取源中读取新的数据块,将数据填充到缓冲区中。
- 在填充数据之前,会进行一些操作,如将未读取的数据移动到缓冲区开头。
5. Peek:预读取字节
// readErr 返回当前的错误并清空错误状态。
func (b *Reader) readErr() error {
err := b.err
b.err = nil
return err
}
解释:
readErr
方法是Reader
结构体的方法,没有显式的接收者,但它使用了b
结构体的字段。- 该方法返回当前的错误(
b.err
)并将错误状态清空。 - 将当前的错误存储在变量
err
中,然后将b.err
设置为nil
。 - 最后,返回存储的错误。
作用:
- 该方法用于获取当前的错误并清空错误状态,以便在需要处理错误时使用。
// Peek 返回下一个 n 个字节而不移动读取器的位置。
// 这些字节在下一次读取调用时不再有效。
// 如果 Peek 返回少于 n 个字节,它还会返回一个解释为为什么读取不完整的错误。
// 如果 n 大于 b 的缓冲区大小,则错误是 [ErrBufferFull]。
//
// 调用 Peek 会阻止 [Reader.UnreadByte] 或 [Reader.UnreadRune] 的调用成功,直到下一次读取操作。
func (b *Reader) Peek(n int) ([]byte, error) {
if n < 0 {
return nil, ErrNegativeCount
}
b.lastByte = -1
b.lastRuneSize = -1
// 当缓冲区中剩余数据不足 n 时,且缓冲区未满且没有错误时,调用 fill 方法填充缓冲区。
for b.w-b.r < n && b.w-b.r < len(b.buf) && b.err == nil {
b.fill() // b.w-b.r < len(b.buf) => buffer is not full
}
if n > len(b.buf) {
return b.buf[b.r:b.w], ErrBufferFull
}
// 0 <= n <= len(b.buf)
var err error
if avail := b.w - b.r; avail < n {
// 缓冲区中的数据不足
n = avail
err = b.readErr()
if err == nil {
err = ErrBufferFull
}
}
return b.buf[b.r : b.r+n], err
}
解释:
Peek
方法是Reader
结构体的方法,没有显式的接收者,但它使用了b
结构体的字段。- 该方法返回下一个
n
个字节而不移动读取器的位置。 - 如果
Peek
返回的字节数少于n
个,它还会返回一个解释为什么读取不完整的错误。 - 如果
n
大于b
的缓冲区大小,则错误是ErrBufferFull
。 - 调用
Peek
会阻止Reader.UnreadByte
或Reader.UnreadRune
的调用成功,直到下一次读取操作。
作用:
Peek
方法用于预读取字节,允许查看但不消耗缓冲区中的数据。- 如果需要查看接下来的字节而不移动读取位置,可以使用
Peek
方法。
6. Discard:丢弃字节
// Discard 跳过接下来的 n 个字节,并返回丢弃的字节数。
// 如果 Discard 跳过的字节数少于 n 个,它也会返回一个错误。
// 如果 0 <= n <= b.Buffered(),则 Discard 保证在不从底层 io.Reader 读取的情况下成功执行。
func (b *Reader) Discard(n int) (discarded int, err error) {
if n < 0 {
return 0, ErrNegativeCount
}
if n == 0 {
return
}
b.lastByte = -1
b.lastRuneSize = -1
remain := n
for {
skip := b.Buffered()
if skip == 0 {
b.fill()
skip = b.Buffered()
}
if skip > remain {
skip = remain
}
b.r += skip
remain -= skip
if remain == 0 {
return n, nil
}
if b.err != nil {
return n - remain, b.readErr()
}
}
}
解释:
Discard
方法是Reader
结构体的方法,没有显式的接收者,但它使用了b
结构体的字段。- 此方法用于跳过接下来的
n
个字节,并返回丢弃的字节数。 - 如果
Discard
跳过的字节数少于n
个,它也会返回一个错误。 - 在
0 <= n <= b.Buffered()
的情况下,Discard
保证在不从底层io.Reader
读取的情况下成功执行。
作用:
Discard
方法允许跳过指定数量的字节而不读取或返回它们,对于消费数据前不需要的部分是很有用的。- 它可以帮助快速丢弃指定数量的字节,而不必将这些字节从底层的
io.Reader
中实际读取出来。