上一篇文章:
Go源码学习:bufio包 - 1.2 - scan.go -(1)
10、Scan:扫描下一个标记
这部分代码定义了 Scan
方法,用于将 [Scanner] 推进到下一个标记,然后可以通过 [Scanner.Bytes] 或 [Scanner.Text] 方法访问该标记。当没有更多标记时,无论是到达输入的末尾还是出现错误,它都会返回false。在 Scan
返回false后,[Scanner.Err] 方法将返回扫描期间发生的任何错误,除非是 [io.EOF],此时 [Scanner.Err] 将返回nil。
如果分割函数返回太多未提前推进输入的空标记,Scan
将引发 panic。这是扫描器的常见错误模式。
func (s *Scanner) Scan() bool {
if s.done {
return false
}
s.scanCalled = true
// 循环直到我们有一个标记。
for {
// 查看我们是否可以使用我们已有的内容获取一个标记。
// 如果我们已经用完了数据但有错误,给分割函数一个机会来恢复任何剩余的、可能为空的标记。
if s.end > s.start || s.err != nil {
advance, token, err := s.split(s.buf[s.start:s.end], s.err != nil)
if err != nil {
if err == ErrFinalToken {
s.token = token
s.done = true
// 当标记不为nil时,表示扫描停止时有一个尾随标记,因此返回值应为true,表示存在标记。
return token != nil
}
s.setErr(err)
return false
}
if !s.advance(advance) {
return false
}
s.token = token
if token != nil {
if s.err == nil || advance > 0 {
s.empties = 0
} else {
// 返回未在EOF时推进输入的标记。
s.empties++
if s.empties > maxConsecutiveEmptyReads {
panic("bufio.Scan: too many empty tokens without progressing")
}
}
return true
}
}
// 我们无法使用我们手头的内容生成标记。
// 如果我们已经达到EOF或出现I/O错误,我们完成了。
if s.err != nil {
// 关闭它。
s.start = 0
s.end = 0
return false
}
// 必须读取更多数据。
// 首先,如果有很多空间或需要空间,将数据移动到缓冲区的开头。
if s.start > 0 && (s.end == len(s.buf) || s.start > len(s.buf)/2) {
copy(s.buf, s.buf[s.start:s.end])
s.end -= s.start
s.start = 0
}
// 缓冲区是否已满?如果是,调整大小。
if s.end == len(s.buf) {
// 保证在下面的乘法中没有溢出。
const maxInt = int(^uint(0) >> 1)
if len(s.buf) >= s.maxTokenSize || len(s.buf) > maxInt/2 {
s.setErr(ErrTooLong)
return false
}
newSize := len(s.buf) * 2
if newSize == 0 {
newSize = startBufSize
}
newSize = min(newSize, s.maxTokenSize)
newBuf := make([]byte, newSize)
copy(newBuf, s.buf[s.start:s.end])
s.buf = newBuf
s.end -= s.start
s.start = 0
}
// 最后,我们可以读取一些输入。确保我们不会因为阻塞的Reader而陷入困境。
// 官方上我们不需要这样做,但让我们格外小心:Scanner是用于安全、简单的任务的。
for loop := 0; ; {
n, err := s.r.Read(s.buf[s.end:len(s.buf)])
if n < 0 || len(s.buf)-s.end < n {
s.setErr(ErrBadReadCount)
break
}
s.end += n
if err != nil {
s.setErr(err)
break
}
if n > 0 {
s.empties = 0
break
}
loop++
if loop > maxConsecutiveEmptyReads {
s.setErr(io.ErrNoProgress)
break
}
}
}
}
解释:
Scan
方法是Scanner
结构体的方法,没有显式的接收者,但它使用了s
结构体的字段。- 首先,如果
s.done
为 true,表示已经扫描完成,直接返回 false。 - 然后,将
s.scanCalled
设置为 true,表示已经调用了扫描方法。 - 接着,通过循环一直到获得一个标记。
- 首先,检查是否可以使用已有的数据获取一个标记,如果数据已经用完但是有错误,给分割函数一个机会来恢复任何剩余的、可能为空的标记。
- 如果分割函数返回错误,根据错误类型进行处理,如果是
ErrFinalToken
,表示扫描停止,将s.token
设置为标记,s.done
设置为 true,返回 true 表示存在标记;否则,设置错误并返回 false。 - 如果成功获取了标记,进行相应的处理,并返回 true 表示存在标记。
- 如果无法生成标记,检查是否已经到达了文件结尾或者出现了 I/O 错误,如果是,清空数据并返回 false。
- 如果需要读取更多数据,进行相应的处理,包括移动数据、调整缓冲区大小、读取输入等。
作用:
Scan
方法的主要作用是扫描下一个标记,将 [Scanner] 推进到下一个标记的位置。- 它会根据已有的数据和读取新的数据来获取标记,同时处理可能出现的错误情况。
- 在扫描过程中,会根据不同的情况进行相应的处理,以确保能够正确地获取标记并推进扫描位置。
11、advance:消耗缓冲区中的 n 个字节
这部分代码定义了 advance
方法,用于消耗缓冲区中的 n 个字节。它报告了这次消耗是否合法。
// advance 消耗缓冲区中的 n 个字节。它报告了这次消耗是否合法。
func (s *Scanner) advance(n int) bool {
if n < 0 {
s.setErr(ErrNegativeAdvance)
return false
}
if n > s.end-s.start {
s.setErr(ErrAdvanceTooFar)
return false
}
s.start += n
return true
}
解释:
advance
方法是Scanner
结构体的方法,没有显式的接收者,但它使用了s
结构体的字段。- 首先,检查消耗的字节数
n
是否小于零,如果是,设置错误并返回 false。 - 然后,检查消耗的字节数
n
是否超出了缓冲区中可用的字节数,如果是,设置错误并返回 false。 - 最后,将缓冲区的起始位置
s.start
向前移动 n 个字节,并返回 true 表示消耗合法。
作用:
advance
方法的主要作用是消耗缓冲区中的 n 个字节,并报告这次消耗是否合法。- 它会根据消耗的字节数进行相应的检查,并更新缓冲区的起始位置。
- 在消耗之前,会进行一些合法性检查,以确保消耗操作的正确性。
12、setErr:记录遇到的第一个错误
这部分代码定义了 setErr
方法,用于记录遇到的第一个错误。
// setErr 记录遇到的第一个错误。
func (s *Scanner) setErr(err error) {
if s.err == nil || s.err == io.EOF {
s.err = err
}
}
解释:
setErr
方法是Scanner
结构体的方法,没有显式的接收者,但它使用了s
结构体的字段。- 首先,检查当前的错误是否为空或者为
io.EOF
,如果是,将遇到的错误记录在s.err
中。
作用:
setErr
方法的主要作用是记录遇到的第一个错误。- 它会检查当前是否已经有错误存在,如果没有,则记录遇到的错误,以便后续处理。
13、Buffer:设置扫描时的初始缓冲区和最大分配缓冲区大小
这部分代码定义了 Buffer
方法,用于设置扫描时的初始缓冲区和最大分配缓冲区大小。最大令牌大小必须小于 max
和 cap(buf)
中较大的那个。如果 max
小于等于 cap(buf)
,[Scanner.Scan] 将仅使用该缓冲区,并且不进行分配。
// Buffer 设置扫描时的初始缓冲区和最大分配缓冲区大小。
// 最大令牌大小必须小于 max 和 cap(buf) 中较大的那个。
// 如果 max 小于等于 cap(buf),[Scanner.Scan] 将仅使用该缓冲区,并且不进行分配。
//
// 默认情况下,[Scanner.Scan] 使用内部缓冲区,并将最大令牌大小设置为 [MaxScanTokenSize]。
//
// 如果在扫描开始后调用,Buffer 将引发 panic。
func (s *Scanner) Buffer(buf []byte, max int) {
if s.scanCalled {
panic("Buffer called after Scan")
}
s.buf = buf[0:cap(buf)]
s.maxTokenSize = max
}
解释:
Buffer
方法是Scanner
结构体的方法,有两个参数:buf
表示初始缓冲区,max
表示最大分配缓冲区大小。- 如果扫描已经开始(
s.scanCalled
为 true),则调用Buffer
将引发 panic。 - 设置
Scanner
结构体的buf
字段为传入缓冲区的切片,并将maxTokenSize
字段设置为最大分配缓冲区大小。
作用:
Buffer
方法的作用是配置扫描时的缓冲区和最大分配缓冲区大小。- 如果用户提供的缓冲区足够大,且最大令牌大小不超过该缓冲区的容量,将避免额外的内存分配。
- 如果在扫描开始后调用,将引发 panic,以确保配置操作在扫描开始之前完成。
14、Split:设置[Scanner]的分割函数
这部分代码定义了 Split
方法,用于设置 [Scanner] 的分割函数。默认的分割函数是 [ScanLines]。
// Split 设置 [Scanner] 的分割函数。
// 默认分割函数是 [ScanLines]。
//
// 如果在扫描开始后调用,Split 将引发 panic。
func (s *Scanner) Split(split SplitFunc) {
if s.scanCalled {
panic("Split called after Scan")
}
s.split = split
}
解释:
Split
方法是Scanner
结构体的方法,有一个参数split
,表示用户自定义的分割函数。- 如果扫描已经开始(
s.scanCalled
为 true),则调用Split
将引发 panic。 - 设置
Scanner
结构体的split
字段为传入的分割函数。
作用:
Split
方法的作用是配置 [Scanner] 使用的分割函数。- 用户可以通过设置自定义的分割函数来定义扫描时如何切分输入数据。
- 如果在扫描开始后调用,将引发 panic,以确保配置操作在扫描开始之前完成。
15、SplitBytes:用于[Scanner]的分割函数
这部分代码定义了 ScanBytes
函数,作为 [Scanner] 的分割函数,它将每个字节作为一个token返回。
// ScanBytes 是 [Scanner] 的分割函数,将每个字节作为一个token返回。
func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
return 1, data[0:1], nil
}
解释:
ScanBytes
是一个分割函数,符合 [Scanner] 的接口规范,接收输入数据data
和是否已经到达文件尾atEOF
两个参数。- 如果已经到达文件尾并且输入数据为空,返回零表示没有更多的令牌。
- 否则,返回一个字节的长度,以及包含第一个字节的切片作为token。
作用:
ScanBytes
用于定义 [Scanner] 在扫描过程中如何切割输入数据,这里是将每个字节作为一个独立的token。
16、errorRune:UTF-8解码错误的字节切片
这部分代码定义了 errorRune
变量,它是一个包含UTF-8解码错误字符的字节切片。
var errorRune = []byte(string(utf8.RuneError))
解释:
errorRune
是一个字节切片,其中包含了UTF-8解码错误字符的字节表示。- 这里使用
utf8.RuneError
获取UTF-8解码错误的特殊字符。
作用:
errorRune
用于表示在UTF-8解码过程中发生错误时的特殊字节序列,通常用于标记无法正确解码的部分。
17、ScanRunes:扫描UTF-8编码的符文
这段代码定义了 ScanRunes
方法,作为 [Scanner] 的分割函数,返回每个UTF-8编码的符文作为一个标记。返回的符文序列等效于对输入字符串进行范围循环的符文序列,这意味着错误的UTF-8编码将转换为U+FFFD = “\xef\xbf\xbd”。由于Scan接口的存在,客户端无法区分正确编码的替代符文和编码错误。
// ScanRunes是[Scanner]的分割函数,返回每个UTF-8编码的符文作为一个标记。
// 返回的符文序列等效于对输入字符串进行范围循环的符文序列,这意味着错误的UTF-8编码将转换为U+FFFD = "\xef\xbf\xbd"。
// 由于Scan接口的存在,这使得客户端无法区分正确编码的替代符文和编码错误。
func ScanRunes(data []byte, atEOF bool) (advance int, token []byte, err error) {
// 如果在EOF并且数据为空,则返回0。
if atEOF && len(data) == 0 {
return 0, nil, nil
}
// 快速路径1:ASCII。
if data[0] < utf8.RuneSelf {
return 1, data[0:1], nil
}
// 快速路径2:正确的UTF-8解码,没有错误。
_, width := utf8.DecodeRune(data)
if width > 1 {
// 这是一个有效的编码。对于正确编码的非ASCII符文,宽度不能为1。
return width, data[0:width], nil
}
// 我们知道这是一个错误:宽度==1且隐式r==utf8.RuneError。
// 错误是因为没有完整的符文可以解码吗?
// FullRune在错误和不完整的编码之间正确地进行了区分。
if !atEOF && !utf8.FullRune(data) {
// 不完整;获取更多字节。
return 0, nil, nil
}
// 我们有一个真正的UTF-8编码错误。返回一个正确编码的错误符文
// 但仅提前一个字节。这与对不正确编码的字符串进行范围循环的行为相匹配。
return 1, errorRune, nil
}
解释:
ScanRunes
方法是一个用于 [Scanner] 的分割函数,按照UTF-8编码将数据分割成符文。- 首先,检查在EOF时并且数据为空的情况,返回0。
- 然后,通过快速路径1检查ASCII字符,如果是则返回1。
- 接着,通过快速路径2检查正确的UTF-8解码,如果是则返回相应的宽度。
- 如果上述条件都不满足,说明存在UTF-8编码错误,根据错误类型采取相应的处理逻辑。
- 最终,返回advance(前进的字节数)、token(符文的字节序列)和错误信息。
作用:
ScanRunes
方法的主要作用是将UTF-8编码的数据按符文分割,提供给 [Scanner] 使用。- 它能正确处理ASCII字符和UTF-8编码错误,确保分割的符文序列是符合规范的。
18、dropCR:去除末尾的\r
这段代码定义了 dropCR
方法,用于从数据中去除末尾的回车符 \r
。
// dropCR从数据中去除末尾的\r。
func dropCR(data []byte) []byte {
// 如果数据长度大于0且末尾是\r,则返回去除末尾的数据。
if len(data) > 0 && data[len(data)-1] == '\r' {
return data[0 : len(data)-1]
}
// 否则返回原始数据。
return data
}
解释:
dropCR
方法接收一个字节数组data
,用于去除该数据末尾的回车符\r
。- 首先,检查数据长度是否大于0且末尾是
\r
,如果是,则返回去除末尾的数据。 - 否则,返回原始数据。
作用:
dropCR
方法的主要作用是处理数据,去除末尾可能存在的回车符\r
。
19、ScanLines:扫描文本行
这段代码定义了 ScanLines
方法,作为 [Scanner] 的分割函数,返回每行文本,去除任何末尾的换行符。返回的行可能为空。换行符是一个可选的回车符,后面跟着一个必需的换行符。在正则表达式表示中,它是 \r?\n
。即最后一个非空行即使没有换行符也会被返回。
// ScanLines是[Scanner]的分割函数,返回每行文本,去除任何末尾的换行符。
// 返回的行可能为空。换行符是一个可选的回车符,后面跟着一个必需的换行符。
// 在正则表达式表示中,它是 \r?\n。即最后一个非空行即使没有换行符也会被返回。
func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
// 如果在EOF时并且数据为空,则返回0。
if atEOF && len(data) == 0 {
return 0, nil, nil
}
// 如果在数据中找到换行符,则返回完整的以换行符终止的行。
if i := bytes.IndexByte(data, '\n'); i >= 0 {
// 我们有一个完整的以换行符终止的行。
return i + 1, dropCR(data[0:i]), nil
}
// 如果在EOF时,有最后一个非终止的行,则返回它。
if atEOF {
return len(data), dropCR(data), nil
}
// 请求更多数据。
return 0, nil, nil
}
解释:
ScanLines
方法是一个用于 [Scanner] 的分割函数,按行返回文本,去除末尾的换行符。- 首先,检查在EOF时并且数据为空的情况,返回0。
- 然后,通过查找数据中的换行符,如果找到则返回完整的以换行符终止的行。
- 如果在EOF时,存在最后一个非终止的行,则返回它。
- 如果上述条件都不满足,说明需要更多数据。
作用:
ScanLines
方法的主要作用是将文本数据按行分割,去除行末尾的换行符。- 它能处理包含换行符的完整行,以及在EOF时的最后一个非终止行。