上一篇:
Go源码学习:bytes包 - 1.2 - buffer.go -(2)
21、MinRead:传递给 Read 调用的最小切片大小
// MinRead 是由 [Buffer.ReadFrom] 传递给 Read 调用的最小切片大小。
// 只要 [Buffer] 至少有 MinRead 字节超出了持有 r 内容所需的内容,ReadFrom 就不会增长底层缓冲区。
const MinRead = 512
解释:
MinRead
是一个常量,表示由 [Buffer.ReadFrom] 传递给 Read 调用的最小切片大小。- 该常量用于控制在进行读取操作时,底层缓冲区的最小大小要求。
作用:
MinRead
的作用是确保在执行读取操作时,底层缓冲区具有足够的空间来容纳要读取的数据,以提高读取的效率和性能。- 通过限定最小切片大小,可以避免在每次读取时频繁地扩展缓冲区的大小,从而提高了读取操作的效率。
22、ReadFrom:从读取器中读取数据并追加到缓冲区中
这部分代码定义了 ReadFrom
方法,用于从读取器 r
中读取数据直到 EOF,并将数据追加到缓冲区中,根据需要扩展缓冲区。返回值 n
是读取的字节数,任何除了 io.EOF 之外的错误在读取过程中也会被返回。如果缓冲区变得太大,ReadFrom
会以 [ErrTooLarge] 引发 panic。
// ReadFrom 从读取器 r 中读取数据直到 EOF,并将数据追加到缓冲区中,根据需要扩展缓冲区。
// 返回值 n 是读取的字节数。任何除了 io.EOF 之外的错误在读取过程中也会被返回。
// 如果缓冲区变得太大,ReadFrom 会以 [ErrTooLarge] 引发 panic。
func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
b.lastRead = opInvalid
for {
// 通过 grow 方法扩展缓冲区,确保有足够的空间来读取数据。
i := b.grow(MinRead)
// 调整缓冲区的长度为 i。
b.buf = b.buf[:i]
// 从读取器 r 中读取数据到缓冲区中。
m, e := r.Read(b.buf[i:cap(b.buf)])
// 如果读取的字节数 m 小于零,抛出 panic,表示读取返回了负数。
if m < 0 {
panic(errNegativeRead)
}
// 调整缓冲区的长度为 i+m,表示成功读取了 m 个字节。
b.buf = b.buf[:i+m]
n += int64(m)
// 如果遇到了 io.EOF,显式地返回 nil,表示到达了文件末尾。
if e == io.EOF {
return n, nil
}
// 如果遇到了其他错误,返回读取的字节数 n 和遇到的错误 e。
if e != nil {
return n, e
}
}
}
解释:
ReadFrom
方法是Buffer
结构体的方法,接收一个读取器r
,并返回读取的字节数和可能遇到的错误。- 首先,将
lastRead
字段设置为opInvalid
,表示最近的读取操作无效。 - 然后,通过循环不断从读取器
r
中读取数据,并将数据追加到缓冲区中。- 使用
grow
方法扩展缓冲区,确保有足够的空间来读取数据。 - 调整缓冲区的长度为
i
。 - 从读取器
r
中读取数据到缓冲区中。 - 如果读取的字节数
m
小于零,抛出 panic,表示读取返回了负数。 - 调整缓冲区的长度为
i+m
,表示成功读取了m
个字节。 - 累加读取的字节数到
n
中。 - 如果遇到了 io.EOF,显式地返回 nil,表示到达了文件末尾。
- 如果遇到了其他错误,返回读取的字节数
n
和遇到的错误e
。
- 使用
作用:
ReadFrom
方法的主要作用是从读取器中读取数据并追加到缓冲区中,实现了一种高效的数据读取方式。- 它会根据需要扩展缓冲区,以适应从读取器中读取的数据量。
- 在读取数据的过程中,会及时处理读取过程中可能遇到的错误,并返回读取的字节数和可能遇到的错误。
23、growSlice:扩展切片
这部分代码定义了 growSlice
函数,用于扩展切片 b
的容量,并保留原始内容。
// growSlice 扩展切片 b 的容量为 n,保留切片 b 的原始内容。
// 如果分配失败,将会以 ErrTooLarge 异常抛出。
func growSlice(b []byte, n int) []byte {
defer func() {
if recover() != nil {
panic(ErrTooLarge)
}
}()
// TODO(http://golang.org/issue/51462):我们应该依赖于追加-创建模式,以便编译器可以调用 runtime.growslice。例如:
// return append(b, make([]byte, n)...)
// 这避免了对分配的切片的前 len(b) 个字节进行不必要的清零,但这种模式会导致切片 b 逃逸到堆上。
//
// 取而代之的是,使用带有空切片的追加-创建模式,以确保我们分配的缓冲区大小舍入到最接近的大小类别。
c := len(b) + n // 确保有足够的空间容纳 n 个元素
if c < 2*cap(b) {
// 增长率在历史上一直是 2 倍。将来,我们可以完全依赖追加来确定增长率。
c = 2 * cap(b)
}
b2 := append([]byte(nil), make([]byte, c)...)
copy(b2, b)
return b2[:len(b)]
}
解释:
growSlice
函数接收一个切片b
和一个整数n
,用于扩展切片b
的容量。- 在函数内部,使用了
defer
语句,用于在函数返回之前执行一段代码,这里用于捕获可能的异常并以ErrTooLarge
异常抛出。 - 首先,计算扩展后的容量
c
,确保有足够的空间容纳n
个元素,并且至少是原始容量的两倍。 - 接着,使用追加-创建模式,创建一个新的切片
b2
,并将原始切片b
的内容复制到新的切片中。 - 最后,返回新的切片
b2
的前len(b)
个元素,即保留原始内容的扩展切片。
作用:
growSlice
函数的主要作用是扩展切片的容量,并且保留原始内容,以满足切片容量的需求。- 它通过重新分配内存并复制原始内容,实现了切片容量的扩展,同时保留了原始数据的完整性。
24、WriteTo:向目标写入数据
这部分代码定义了 WriteTo
方法,用于将数据写入目标 w
,直到缓冲区耗尽或发生错误。
// WriteTo 方法向目标 w 写入数据,直到缓冲区耗尽或发生错误。
// 返回值 n 是写入的字节数;它总是适合 int 类型,但为了匹配 io.WriterTo 接口,它是 int64 类型。
// 在写入过程中遇到的任何错误也将被返回。
func (b *Buffer) WriteTo(w io.Writer) (n int64, err error) {
b.lastRead = opInvalid
if nBytes := b.Len(); nBytes > 0 {
m, e := w.Write(b.buf[b.off:])
if m > nBytes {
panic("bytes.Buffer.WriteTo: invalid Write count")
}
b.off += m
n = int64(m)
if e != nil {
return n, e
}
// 根据 io.Writer 中 Write 方法的定义,所有字节应该已经被写入。
if m != nBytes {
return n, io.ErrShortWrite
}
}
// 缓冲区现在为空;重置。
b.Reset()
return n, nil
}
解释:
WriteTo
方法是Buffer
结构体的方法,用于将缓冲区中的数据写入目标w
。- 首先,将
lastRead
标记为opInvalid
,表示最近的读取操作无效。 - 然后,检查缓冲区中是否有剩余数据(
nBytes > 0
)。- 如果有剩余数据,尝试向目标
w
写入数据,并获取写入的字节数m
和可能的错误e
。 - 如果写入的字节数
m
大于剩余数据的字节数nBytes
,抛出 panic,表示写入计数无效。 - 更新偏移量
b.off
,表示成功写入了m
个字节。 - 将写入的字节数
m
转换为 int64 类型,赋值给返回值n
。 - 如果在写入过程中遇到错误
e
,直接返回写入的字节数和错误。 - 最后,检查是否所有的字节都已经被写入,如果不是,返回写入的字节数和错误
io.ErrShortWrite
。
- 如果有剩余数据,尝试向目标
- 如果缓冲区现在为空,调用
b.Reset()
方法重置缓冲区。 - 最后,返回写入的字节数
n
和nil
错误。
作用:
WriteTo
方法的作用是将缓冲区中的数据写入目标w
,并返回写入的字节数和可能的错误。- 它确保缓冲区中的数据被完整地写入目标,同时在写入完成后重置缓冲区,以便下一次写入操作。
25、WriteByte:向缓冲区写入字节
这部分代码定义了 WriteByte
方法,用于向缓冲区追加字节 c
,并在需要时扩展缓冲区的大小。
// WriteByte 方法向缓冲区追加字节 c,根据需要扩展缓冲区的大小。
// 返回的错误始终为 nil,但包含在此处以匹配 bufio.Writer 的 WriteByte。
// 如果缓冲区变得太大,WriteByte 将以 ErrTooLarge 异常抛出。
func (b *Buffer) WriteByte(c byte) error {
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(1)
if !ok {
m = b.grow(1)
}
b.buf[m] = c
return nil
}
解释:
WriteByte
方法是Buffer
结构体的方法,用于向缓冲区追加字节c
,并在需要时扩展缓冲区的大小。- 首先,将
lastRead
标记为opInvalid
,表示最近的读取操作无效。 - 然后,尝试通过
tryGrowByReslice
方法扩展缓冲区的大小,以容纳新的字节。- 如果扩展成功(
ok == true
),则直接将字节c
写入扩展后的位置。 - 如果扩展失败(
ok == false
),则调用grow
方法扩展缓冲区的大小,并将字节c
写入扩展后的位置。
- 如果扩展成功(
- 最后,返回的错误始终为 nil,但包含在此处以匹配 bufio.Writer 的 WriteByte。
作用:
WriteByte
方法的作用是向缓冲区追加单个字节,并在需要时扩展缓冲区的大小,以容纳新的字节。- 它确保缓冲区足够大以容纳新的字节,并将字节写入缓冲区的末尾。
26、WriteRune:向缓冲区写入 Unicode 字符的 UTF-8 编码
这部分代码定义了 WriteRune
方法,用于向缓冲区追加 Unicode 字符 r
的 UTF-8 编码,并返回其长度和一个始终为 nil 的错误,以匹配 bufio.Writer 的 WriteRune。
// WriteRune 方法向缓冲区追加 Unicode 字符 r 的 UTF-8 编码,返回其长度和一个始终为 nil 的错误,以匹配 bufio.Writer 的 WriteRune。
// 如果需要,缓冲区会根据需要进行扩展;如果缓冲区变得太大,WriteRune 将以 ErrTooLarge 异常抛出。
func (b *Buffer) WriteRune(r rune) (n int, err error) {
// 以 uint32 类型进行比较,以正确处理负数符文。
if uint32(r) < utf8.RuneSelf {
b.WriteByte(byte(r))
return 1, nil
}
b.lastRead = opInvalid
m, ok := b.tryGrowByReslice(utf8.UTFMax)
if !ok {
m = b.grow(utf8.UTFMax)
}
b.buf = utf8.AppendRune(b.buf[:m], r)
return len(b.buf) - m, nil
}
解释:
WriteRune
方法是Buffer
结构体的方法,用于向缓冲区追加 Unicode 字符r
的 UTF-8 编码,并返回其长度和一个始终为 nil 的错误,以匹配 bufio.Writer 的 WriteRune。- 首先,通过与
utf8.RuneSelf
的比较,判断是否可以使用一个字节表示该 Unicode 字符,如果可以,则直接将该字节写入缓冲区,并返回长度为 1 和 nil 错误。 - 如果需要多个字节表示该 Unicode 字符,尝试通过
tryGrowByReslice
方法扩展缓冲区的大小,以容纳 UTF-8 编码所需的最大字节数(utf8.UTFMax
)。- 如果扩展成功(
ok == true
),则将 Unicode 字符r
的 UTF-8 编码追加到缓冲区中。 - 如果扩展失败(
ok == false
),则调用grow
方法扩展缓冲区的大小,并将 Unicode 字符r
的 UTF-8 编码追加到缓冲区中。
- 如果扩展成功(
- 最后,返回 UTF-8 编码的长度和一个始终为 nil 的错误。
作用:
WriteRune
方法的作用是向缓冲区追加 Unicode 字符的 UTF-8 编码,确保缓冲区足够大以容纳编码后的字节序列,并返回编码后的长度和 nil 错误。
27、Read:从缓冲区读取数据
这部分代码定义了 Read
方法,用于从缓冲区读取下一个 len(p)
个字节的数据,或者直到缓冲区被耗尽。返回值 n
是读取的字节数。如果缓冲区没有数据可返回,err
为 io.EOF(除非 len(p)
为零);否则为 nil。
// Read 方法从缓冲区读取下一个 len(p) 个字节的数据,或者直到缓冲区被耗尽。返回值 n 是读取的字节数。如果缓冲区没有数据可返回,err 为 io.EOF(除非 len(p) 为零);否则为 nil。
func (b *Buffer) Read(p []byte) (n int, err error) {
b.lastRead = opInvalid
if b.empty() {
// 缓冲区为空,重置以释放空间。
b.Reset()
if len(p) == 0 {
return 0, nil
}
return 0, io.EOF
}
n = copy(p, b.buf[b.off:])
b.off += n
if n > 0 {
b.lastRead = opRead
}
return n, nil
}
解释:
Read
方法是Buffer
结构体的方法,用于从缓冲区读取下一个len(p)
个字节的数据,或者直到缓冲区被耗尽。- 首先,将
lastRead
标记设置为opInvalid
。 - 如果缓冲区为空,重置缓冲区以释放空间,并根据情况返回 0 和 nil,或者返回 0 和 io.EOF。
- 否则,将缓冲区中的数据拷贝到
p
中,更新读取位置b.off
,并根据读取的字节数设置lastRead
标记为opRead
。 - 最后,返回读取的字节数和 nil 错误,或者返回 0 和 io.EOF。
作用:
Read
方法的作用是从缓冲区读取数据,将数据拷贝到指定的字节切片中,并更新缓冲区的读取位置。如果缓冲区为空,则重置缓冲区以释放空间,并根据情况返回相应的值和错误。
28、Next:获取缓冲区的下一个字节切片
这部分代码定义了 Next
方法,用于返回包含缓冲区中下一个 n
个字节的切片,同时将缓冲区的读取位置向前移动 n
个字节,就像这些字节已经被 Buffer.Read
方法返回一样。如果缓冲区中的字节数少于 n
,则 Next
方法返回整个缓冲区。
// Next 方法返回一个包含缓冲区中下一个 n 个字节的切片,同时将缓冲区的读取位置向前移动 n 个字节,就像这些字节已经被 Buffer.Read 方法返回一样。如果缓冲区中的字节数少于 n,则 Next 方法返回整个缓冲区。
func (b *Buffer) Next(n int) []byte {
b.lastRead = opInvalid
m := b.Len()
if n > m {
n = m
}
data := b.buf[b.off : b.off+n]
b.off += n
if n > 0 {
b.lastRead = opRead
}
return data
}
解释:
Next
方法是Buffer
结构体的方法,用于返回一个包含缓冲区中下一个n
个字节的切片,并将缓冲区的读取位置向前移动n
个字节,就像这些字节已经被Buffer.Read
方法返回一样。- 首先,将
lastRead
标记设置为opInvalid
。 - 然后,计算缓冲区中剩余的字节数
m
,并将n
限制为不超过m
。 - 接着,获取包含下一个
n
个字节的切片data
,并将读取位置向前移动n
个字节。 - 如果成功获取了字节切片,将
lastRead
标记设置为opRead
。 - 最后,返回字节切片
data
。
作用:
Next
方法的作用是返回缓冲区中的下一个n
个字节的切片,并将缓冲区的读取位置向前移动n
个字节,以便后续读取操作可以从正确的位置开始。如果缓冲区中的字节数少于n
,则返回整个缓冲区的内容。
29、ReadByte:从缓冲区读取一个字节
这部分代码定义了 ReadByte
方法,用于从缓冲区读取并返回下一个字节。如果没有可用的字节,它会返回错误 io.EOF。
// ReadByte 方法从缓冲区读取并返回下一个字节。如果没有可用的字节,它会返回错误 io.EOF。
func (b *Buffer) ReadByte() (byte, error) {
if b.empty() {
// 缓冲区为空,重置以释放空间。
b.Reset()
return 0, io.EOF
}
c := b.buf[b.off]
b.off++
b.lastRead = opRead
return c, nil
}
解释:
ReadByte
方法是Buffer
结构体的方法,用于从缓冲区读取并返回下一个字节。- 首先,检查缓冲区是否为空,如果是,重置缓冲区以释放空间,并返回错误 io.EOF。
- 否则,获取缓冲区中的下一个字节
c
,并将读取位置向前移动一个字节。 - 最后,将读取操作标记设置为
opRead
,并返回读取的字节和 nil 错误。
作用:
ReadByte
方法的作用是从缓冲区读取并返回下一个字节,同时更新缓冲区的读取位置。如果缓冲区为空,则返回错误 io.EOF。
30、ReadRune:从缓冲区读取一个 Unicode 字符
这部分代码定义了 ReadRune
方法,用于从缓冲区读取并返回下一个 UTF-8 编码的 Unicode 码点。如果没有可用的字节,它会返回错误 io.EOF。如果字节是错误的 UTF-8 编码,它会消耗一个字节并返回 U+FFFD 和 1。
// ReadRune 方法从缓冲区读取并返回下一个 UTF-8 编码的 Unicode 码点。如果没有可用的字节,它会返回错误 io.EOF。如果字节是错误的 UTF-8 编码,它会消耗一个字节并返回 U+FFFD 和 1。
func (b *Buffer) ReadRune() (r rune, size int, err error) {
if b.empty() {
// 缓冲区为空,重置以释放空间。
b.Reset()
return 0, 0, io.EOF
}
c := b.buf[b.off]
if c < utf8.RuneSelf {
b.off++
b.lastRead = opReadRune1
return rune(c), 1, nil
}
r, n := utf8.DecodeRune(b.buf[b.off:])
b.off += n
b.lastRead = readOp(n)
return r, n, nil
}
解释:
ReadRune
方法是Buffer
结构体的方法,用于从缓冲区读取并返回下一个 UTF-8 编码的 Unicode 码点。- 首先,检查缓冲区是否为空,如果是,重置缓冲区以释放空间,并返回错误 io.EOF。
- 接着,获取缓冲区中的下一个字节
c
,并根据字节的值判断是否为单字节 UTF-8 编码。- 如果是单字节编码,将读取位置向前移动一个字节,返回对应的 Unicode 码点和字节数 1。
- 如果不是单字节编码,使用
utf8.DecodeRune
解析缓冲区中的 UTF-8 编码,获取 Unicode 码点r
和编码所占字节数n
。
- 最后,根据解析结果设置读取操作标记,并返回 Unicode 码点、编码字节数和 nil 错误。
作用:
ReadRune
方法的作用是从缓冲区读取并返回下一个 UTF-8 编码的 Unicode 码点,并根据情况返回相应的错误。如果缓冲区为空,将返回错误 io.EOF;如果字节是错误的 UTF-8 编码,将返回 U+FFFD 和 1。