Go源码学习:bufio包 - 1.1 - bufio.go -(2)

发布时间:2023年12月28日

bufio包官方文档

Go源码学习-索引目录

bufio包学习的上一篇文章:Go源码学习:bufio包 - 1.1 - bufio.go -(1)

7、Read:读取数据

// Read 读取数据到 p 中。
// 它返回读取到 p 中的字节数。
// 这些字节最多来自底层 [Reader] 的一次读取,
// 因此 n 可能小于 len(p)。
// 要精确读取 len(p) 字节,请使用 io.ReadFull(b, p)。
// 如果底层 [Reader] 可以在 io.EOF 时返回非零计数,
// 则此 Read 方法也可以这样做;参见 [io.Reader] 文档。
func (b *Reader) Read(p []byte) (n int, err error) {
	n = len(p)
	if n == 0 {
		if b.Buffered() > 0 {
			return 0, nil
		}
		return 0, b.readErr()
	}
	if b.r == b.w {
		if b.err != nil {
			return 0, b.readErr()
		}
		if len(p) >= len(b.buf) {
			// Large read, empty buffer.
			// Read directly into p to avoid copy.
			n, b.err = b.rd.Read(p)
			if n < 0 {
				panic(errNegativeRead)
			}
			if n > 0 {
				b.lastByte = int(p[n-1])
				b.lastRuneSize = -1
			}
			return n, b.readErr()
		}
		// One read.
		// Do not use b.fill, which will loop.
		b.r = 0
		b.w = 0
		n, b.err = b.rd.Read(b.buf)
		if n < 0 {
			panic(errNegativeRead)
		}
		if n == 0 {
			return 0, b.readErr()
		}
		b.w += n
	}

	// copy as much as we can
	// Note: if the slice panics here, it is probably because
	// the underlying reader returned a bad count. See issue 49795.
	n = copy(p, b.buf[b.r:b.w])
	b.r += n
	b.lastByte = int(b.buf[b.r-1])
	b.lastRuneSize = -1
	return n, nil
}

解释:

  • Read 方法是 Reader 结构体的方法,没有显式的接收者,但它使用了 b 结构体的字段。
  • 此方法用于读取数据到切片 p 中。
  • 返回值 n 表示读取到 p 中的字节数。
  • 如果 n 为零,且缓冲区中有数据,则直接返回零,表示成功读取了零字节。
  • 如果缓冲区中没有数据,判断是否需要从底层 Reader 中读取新数据。
  • 如果缓冲区的读指针 b.r 等于写指针 b.w,表示缓冲区为空,需要从底层 Reader 中读取数据。
    • 如果存在错误 b.err,返回读取错误。
    • 如果切片 p 的长度大于等于缓冲区的长度 len(b.buf),采用直接读取到 p 的方式,避免中间复制。
    • 否则,进行一次读取操作,不使用 b.fill 避免循环调用。
  • 如果缓冲区中有数据,使用 copy 函数将尽可能多的数据从缓冲区复制到切片 p 中。
  • 更新读指针 b.r,记录最后一个字节和最后一个符文的大小。
  • 返回实际读取的字节数 n

作用:

  • Read 方法用于从 Reader 中读取数据到指定的切片中。
  • 它会优先从缓冲区中读取数据,如果缓冲区为空,则会从底层的 Reader 中读取数据。
  • 该方法返回读取的字节数以及可能出现的错误。

8、ReadByte:读取单个字节

// ReadByte 读取并返回一个字节。
// 如果没有可用的字节,则返回错误。
func (b *Reader) ReadByte() (byte, error) {
    b.lastRuneSize = -1
    for b.r == b.w {
        if b.err != nil {
            return 0, b.readErr()
        }
        b.fill() // 缓冲区为空时填充
    }
    c := b.buf[b.r]
    b.r++
    b.lastByte = int(c)
    return c, nil
}

解释:

  • ReadByteReader 结构体的方法,用于读取并返回一个字节。
  • 如果缓冲区中没有可用的字节,会调用 fill 方法进行填充。
  • 循环检查读指针 b.r 是否等于写指针 b.w,如果相等表示缓冲区为空,需要填充。
  • 如果存在错误 b.err,则返回读取错误。
  • 从缓冲区中读取一个字节 c,并将读指针 b.r 向前移动。
  • 记录最后一个字节的整数值,并返回读取到的字节和 nil 错误。

作用:

  • ReadByte 方法用于从缓冲区中读取一个字节。
  • 如果缓冲区为空,会先进行填充。
  • 返回读取到的字节和可能的错误。

9、UnreadByte:撤销最近读取的字节

// UnreadByte 撤销最近读取的字节。只有最近读取的字节可以被撤销。
//
// 如果最近调用 [Reader] 上的方法不是读取操作,则 UnreadByte 会返回错误。
// 特别地,[Reader.Peek]、[Reader.Discard] 和 [Reader.WriteTo] 不被认为是读取操作。
func (b *Reader) UnreadByte() error {
    // 如果最近的字节值为负值(表示未读取),或者读指针为0但写指针大于0(表示有未读取的数据),返回无效的撤销字节错误。
    if b.lastByte < 0 || b.r == 0 && b.w > 0 {
        return ErrInvalidUnreadByte
    }
    // 如果读指针大于0,表示有已读取的数据,将读指针前移一位。
    if b.r > 0 {
        b.r--
    } else {
        // 如果读指针为0且写指针为0,表示缓冲区为空,将写指针设置为1。
        b.w = 1
    }
    // 将最近读取的字节值写回缓冲区的读指针位置。
    b.buf[b.r] = byte(b.lastByte)
    // 重置最近读取的字节和最近 rune 大小的状态。
    b.lastByte = -1
    b.lastRuneSize = -1
    return nil
}

解释:

  • UnreadByte 方法是 Reader 结构体的方法,用于撤销最近读取的字节。
  • 首先,检查最近的字节值是否为负值(表示未读取),或者读指针为0但写指针大于0(表示有未读取的数据),如果是,则返回无效的撤销字节错误。
  • 如果读指针大于0,表示有已读取的数据,将读指针前移一位;否则,如果读指针为0且写指针为0,表示缓冲区为空,将写指针设置为1。
  • 将最近读取的字节值写回缓冲区的读指针位置。
  • 最后,重置最近读取的字节和最近 rune 大小的状态,并返回 nil 表示成功。

作用:

  • UnreadByte 方法允许撤销最近成功读取的字节,将读指针前移,使得该字节重新可用。
  • 这个方法在需要回退一个字节时非常有用,例如在某些解析器中解析时需要回退到之前的状态。

10、ReadRune:读取单个UTF-8编码的Unicode字符

// ReadRune 读取单个UTF-8编码的Unicode字符,并返回该字符及其字节大小。
// 如果编码的字符无效,它会消耗一个字节并返回unicode.ReplacementChar(U+FFFD)和大小为1。
func (b *Reader) ReadRune() (r rune, size int, err error) {
    // 循环直到缓冲区中有足够的数据以确保完整的rune被读取,
    // 或者达到缓冲区末尾,或者遇到错误。
    for b.r+utf8.UTFMax > b.w && !utf8.FullRune(b.buf[b.r:b.w]) && b.err == nil && b.w-b.r < len(b.buf) {
        b.fill() // b.w-b.r < len(buf) => buffer is not full
    }
    // 重置最近rune的大小。
    b.lastRuneSize = -1
    // 如果读取指针达到写指针,表示缓冲区为空,返回读取错误。
    if b.r == b.w {
        return 0, 0, b.readErr()
    }
    // 读取一个rune,并设置默认大小为1。
    r, size = rune(b.buf[b.r]), 1
    // 如果rune的值大于或等于utf8.RuneSelf,则表示可能占用多个字节,使用utf8.DecodeRune进行解码。
    if r >= utf8.RuneSelf {
        r, size = utf8.DecodeRune(b.buf[b.r:b.w])
    }
    // 更新读指针和记录最近读取的字节和rune的信息。
    b.r += size
    b.lastByte = int(b.buf[b.r-1])
    b.lastRuneSize = size
    return r, size, nil
}

解释:

  • ReadRune 方法是 Reader 结构体的方法,用于读取单个UTF-8编码的Unicode字符。
  • 首先,通过循环确保缓冲区中有足够的数据以确保完整的rune被读取。
  • 如果缓冲区为空,返回读取错误。
  • 读取一个rune,并设置默认大小为1。
  • 如果rune的值大于或等于utf8.RuneSelf,则表示可能占用多个字节,使用utf8.DecodeRune进行解码。
  • 更新读指针和记录最近读取的字节和rune的信息。
  • 返回读取的rune、大小和nil表示无错误。

作用:

  • ReadRune 方法用于从缓冲区中读取一个UTF-8编码的Unicode字符。
  • 如果缓冲区中没有足够的数据以确保完整的rune被读取,会调用fill方法填充缓冲区。
  • 方法还处理了可能的错误情况,例如缓冲区为空或编码的字符无效。

11、UnreadRune:撤销最后读取的rune

// UnreadRune 撤销最后读取的rune。如果最近在[Reader]上调用的方法不是[Reader.ReadRune],
// [Reader.UnreadRune]会返回错误(在这方面它比[Reader.UnreadByte]更严格,后者将从任何读取操作中撤销最后一个字节)。
func (b *Reader) UnreadRune() error {
    // 如果最近的rune的大小小于零或者读指针小于rune的大小,返回无效的rune撤销错误。
    if b.lastRuneSize < 0 || b.r < b.lastRuneSize {
        return ErrInvalidUnreadRune
    }
    // 撤销rune,更新读指针和记录最近读取的字节和rune的信息。
    b.r -= b.lastRuneSize
    b.lastByte = -1
    b.lastRuneSize = -1
    return nil
}

解释:

  • UnreadRune 方法是 Reader 结构体的方法,用于撤销最后读取的rune。
  • 首先,检查最近的rune的大小是否小于零或者读指针是否小于rune的大小,如果是,返回无效的rune撤销错误。
  • 然后,撤销rune,更新读指针和记录最近读取的字节和rune的信息。
  • 返回nil表示无错误。

作用:

  • UnreadRune 方法用于在不改变读取状态的情况下撤销最后读取的rune。
  • 它会检查是否满足条件进行撤销,如果不满足条件则返回错误。
  • 撤销操作将更新读取指针和记录的最近读取的字节和rune的信息。

12、ReadSlice:读取直到定界符的切片

// Buffered 返回当前缓冲区中可读取的字节数。
func (b *Reader) Buffered() int { return b.w - b.r }

// ReadSlice 读取直到输入中第一个定界符的位置,返回一个指向缓冲区中字节的切片。
// 这些字节在下一次读取时将不再有效。
// 如果 ReadSlice 在找到定界符之前遇到错误,
// 它将返回缓冲区中的所有数据和错误本身(通常是 io.EOF)。
// 如果缓冲区在没有定界符的情况下填满,ReadSlice 失败,返回错误 ErrBufferFull。
// 由于从 ReadSlice 返回的数据将被下一次 I/O 操作覆盖,
// 大多数客户端应该使用 [Reader.ReadBytes] 或 ReadString 来代替。
// 如果 line 不以定界符结束,ReadSlice 返回 err != nil。
func (b *Reader) ReadSlice(delim byte) (line []byte, err error) {
    s := 0 // 搜索开始索引
    for {
        // 在缓冲区中搜索定界符。
        if i := bytes.IndexByte(b.buf[b.r+s:b.w], delim); i >= 0 {
            i += s
            line = b.buf[b.r : b.r+i+1]
            b.r += i + 1
            break
        }

        // 有挂起的错误吗?
        if b.err != nil {
            line = b.buf[b.r:b.w]
            b.r = b.w
            err = b.readErr()
            break
        }

        // 缓冲区是否已满?
        if b.Buffered() >= len(b.buf) {
            b.r = b.w
            line = b.buf
            err = ErrBufferFull
            break
        }

        s = b.w - b.r // 不要重新扫描之前已经扫描过的区域

        b.fill() // 缓冲区未满时进行填充
    }

    // 处理最后一个字节(如果有的话)。
    if i := len(line) - 1; i >= 0 {
        b.lastByte = int(line[i])
        b.lastRuneSize = -1
    }

    return
}

解释:

  • ReadSlice 方法是 Reader 结构体的方法,用于从缓冲区中读取直到指定定界符的切片。
  • 首先,初始化搜索开始索引 s 为零。
  • 使用循环来搜索缓冲区中的定界符。
    • 如果找到定界符,构建切片 line 指向缓冲区中的字节,并更新读指针 b.r
    • 如果存在挂起的错误,返回缓冲区中的所有数据和错误本身,并更新读指针。
    • 如果缓冲区填满而没有找到定界符,返回缓冲区和错误 ErrBufferFull。
    • 如果以上条件都不满足,填充缓冲区并继续搜索。
  • 处理最后一个字节,更新记录的最后一个字节和rune的信息。
  • 返回读取的切片和可能的错误。

作用:

  • ReadSlice 方法用于从缓冲区中读取直到指定定界符的切片。
  • 它提供了一种方便的方式来处理包含定界符的数据。
  • 注意,由于返回的数据会被下一次 I/O 操作覆盖,大多数客户端应该使用 [Reader.ReadBytes] 或 ReadString 来代替。

13、ReadLine:读取一行数据

这部分代码定义了 ReadLine 方法,用于低级别的读取一行数据。大多数调用者应该使用 Reader.ReadBytes('\n')Reader.ReadString('\n'),或者使用 Scanner

// ReadLine 是一个低级别的读取一行数据的原语。大多数调用者应该使用
// [Reader.ReadBytes]('\n') 或 [Reader.ReadString]('\n'),或者使用 [Scanner]。
//
// ReadLine 试图返回一行数据,不包括行尾的字节。
// 如果行太长无法容纳在缓冲区中,则 isPrefix 被设置为 true,返回行的开头。
// 行的其余部分将在将来的调用中返回。当返回行的最后一部分时,isPrefix 将为 false。
// ReadLine 要么返回一个非空的行,要么返回一个错误,而不会同时返回两者。
//
// 从 ReadLine 返回的文本不包括行尾 ("\r\n" 或 "\n")。
// 如果输入在没有最终行尾的情况下结束,将不会给出指示或错误。
// 在调用 ReadLine 后调用 [Reader.UnreadByte] 将始终取消读取最后一个字节
// (可能是属于行尾的字符),即使该字节不是 ReadLine 返回的行的一部分。
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) {
    line, err = b.ReadSlice('\n')
    if err == ErrBufferFull {
        // 处理 "\r\n" 横跨缓冲区的情况。
        if len(line) > 0 && line[len(line)-1] == '\r' {
            // 将 '\r' 放回缓冲区并从 line 中删除。
            // 让下一次调用 ReadLine 检查 "\r\n"。
            if b.r == 0 {
                // 不应该达到的地方
                panic("bufio: tried to rewind past start of buffer")
            }
            b.r--
            line = line[:len(line)-1]
        }
        return line, true, nil
    }

    if len(line) == 0 {
        if err != nil {
            line = nil
        }
        return
    }
    err = nil

    if line[len(line)-1] == '\n' {
        drop := 1
        if len(line) > 1 && line[len(line)-2] == '\r' {
            drop = 2
        }
        line = line[:len(line)-drop]
    }
    return
}

解释:

  • ReadLine 方法是 Reader 结构体的方法,用于读取一行数据。
  • 首先,调用 ReadSlice('\n') 方法读取一行数据,返回的 line 是不包括行尾的。
  • 如果读取时发现缓冲区已满,且行尾跨越缓冲区,特别处理这种情况,将 \r 放回缓冲区,同时从 line 中删除。
  • 如果读取的行长度为零且有错误,将 line 置为 nil
  • 如果成功读取了行数据,且行的最后一个字符是 \n,则删除 \n 及其前面可能的 \r
  • 返回读取到的行数据 line,是否为行的前缀 isPrefix,以及可能存在的错误 err

作用:

  • ReadLine 方法提供了一个低级别的接口,用于读取一行数据。
  • 通过 ReadSlice 实现行的读取,并处理了行尾跨越缓冲区的情况。
  • 返回的 line 不包括行尾的字节,isPrefix 指示是否还有剩余部分未读取,err 表示是否有错误发生。

14、collectFragments:收集片段

这部分代码定义了 collectFragments 方法,用于读取输入直到第一次出现分隔符 delim。它返回一个结果元组:(完整缓冲区的切片,分隔符之前的剩余字节,前两个元素的总字节数,错误)。

完整的结果等同于 bytes.Join(append(fullBuffers, finalFragment), nil),其长度为 totalLen。为了允许调用者最小化分配和复制,结果被结构化成这样。

func (b *Reader) collectFragments(delim byte) (fullBuffers [][]byte, finalFragment []byte, totalLen int, err error) {
    var frag []byte
    // 使用 ReadSlice 查找 delim,累积完整缓冲区。
    for {
        var e error
        frag, e = b.ReadSlice(delim)
        if e == nil { // 获取最后一个片段
            break
        }
        if e != ErrBufferFull { // 意外的错误
            err = e
            break
        }

        // 复制缓冲区。
        buf := bytes.Clone(frag)
        fullBuffers = append(fullBuffers, buf)
        totalLen += len(buf)
    }

    totalLen += len(frag)
    return fullBuffers, frag, totalLen, err
}

解释:

  • collectFragments 方法是 Reader 结构体的方法,用于从输入中读取数据,直到第一次出现分隔符 delim
  • 首先,声明变量 frag 用于保存每次读取的片段。
  • 使用 ReadSlice 方法循环查找分隔符 delim,同时累积完整的缓冲区。
    • 如果成功找到分隔符,表示获取到最后一个片段,跳出循环。
    • 如果返回的错误不是 ErrBufferFull,表示发生了意外错误,将错误存储在 err 中,跳出循环。
    • 如果返回的错误是 ErrBufferFull,表示缓冲区已满,将缓冲区的副本添加到 fullBuffers 中,并累加总字节数。
  • 将最后一个片段 frag 的长度累加到总字节数。
  • 返回完整缓冲区的切片 fullBuffers,最后一个片段 frag,前两个元素的总字节数 totalLen,以及可能存在的错误 err

作用:

  • collectFragments 方法提供了一个在读取数据时累积完整缓冲区的机制。
  • 它通过循环使用 ReadSlice 方法查找分隔符 delim,并在发现分隔符之前累积完整的缓冲区。
  • 返回的结果允许调用者最小化内存分配和数据复制。

15、ReadBytes:读取字节

这部分代码定义了 ReadBytes 方法,用于读取输入直到第一次出现分隔符 delim,返回包含数据和分隔符在内的切片。如果在找到分隔符之前遇到错误,它将返回错误之前读取的数据和错误本身(通常是 io.EOF)。ReadBytes 只在返回的数据不以分隔符结束时才返回 err != nil。对于简单的用途,可能更方便使用 Scanner。

func (b *Reader) ReadBytes(delim byte) ([]byte, error) {
    // 使用 collectFragments 读取数据直到分隔符。
    full, frag, n, err := b.collectFragments(delim)
    
    // 为了容纳完整的片段和碎片,分配新的缓冲区。
    buf := make([]byte, n)
    n = 0
    
    // 将完整的片段和碎片复制到新的缓冲区中。
    for i := range full {
        n += copy(buf[n:], full[i])
    }
    copy(buf[n:], frag)
    
    return buf, err
}

解释:

  • ReadBytes 方法是 Reader 结构体的方法,用于读取输入直到第一次出现分隔符 delim
  • 调用 collectFragments 方法获取完整缓冲区的切片 full、最后一个片段 frag、前两个元素的总字节数 n 和可能存在的错误 err
  • 为了容纳完整的片段和最后一个片段,分配了一个新的缓冲区 buf,长度为 n
  • 使用循环将完整的片段和最后一个片段复制到新的缓冲区中。
  • 返回包含数据和分隔符在内的新缓冲区 buf 和可能存在的错误 err

作用:

  • ReadBytes 方法提供了一种读取数据直到特定分隔符的方式,并返回包含分隔符在内的切片。
  • 通过调用 collectFragments 方法实现了从输入中读取数据的过程。
  • 为了方便使用,将完整的片段和最后一个片段合并到一个新的缓冲区中返回。

16、ReadString:读取字符串

这部分代码定义了 ReadString 方法,用于读取输入直到第一次出现分隔符 delim,返回包含数据和分隔符在内的字符串。如果在找到分隔符之前遇到错误,它将返回错误之前读取的数据和错误本身(通常是 io.EOF)。ReadString 只在返回的数据不以分隔符结束时才返回 err != nil。对于简单的用途,可能更方便使用 Scanner。

func (b *Reader) ReadString(delim byte) (string, error) {
    // 使用 collectFragments 读取数据直到分隔符。
    full, frag, n, err := b.collectFragments(delim)
    
    // 为了容纳完整的片段和碎片,分配新的字符串构建器。
    var buf strings.Builder
    buf.Grow(n)
    
    // 将完整的片段和碎片写入字符串构建器。
    for _, fb := range full {
        buf.Write(fb)
    }
    buf.Write(frag)
    
    return buf.String(), err
}

解释:

  • ReadString 方法是 Reader 结构体的方法,用于读取输入直到第一次出现分隔符 delim
  • 调用 collectFragments 方法获取完整缓冲区的切片 full、最后一个片段 frag、前两个元素的总字节数 n 和可能存在的错误 err
  • 为了容纳完整的片段和最后一个片段,分配了一个新的字符串构建器 buf,并预先分配足够的空间。
  • 使用循环将完整的片段和最后一个片段写入字符串构建器中。
  • 返回包含数据和分隔符在内的字符串和可能存在的错误 err

作用:

  • ReadString 方法提供了一种读取数据直到特定分隔符的方式,并返回包含分隔符在内的字符串。
  • 通过调用 collectFragments 方法实现了从输入中读取数据的过程。
  • 为了方便使用,将完整的片段和最后一个片段合并到一个新的字符串中返回。
文章来源:https://blog.csdn.net/weixin_49015143/article/details/135182305
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。