Go jdk版本1.10.3 Scanner源码在bufio包中的scan.go文件中
Scanner提供一个按指定规则来读取数据的缓冲区IO,比如通过”行匹配函数”来逐行读取数据,Scanner通过Scan方法来按”匹配函数”读取数。
Scan方法会通过一个“匹配函数”读取数据中符合要求的部分,跳过不符合要求的部分。
“匹配函数”由调用者指定。本包中提供的匹配函数有"行匹配函数"、"字节匹配函数"、"字符匹配函数"、"单词匹配函数",用户也可以自定义"匹配函数"。默认的匹配函数为"行匹配函数",用于获取数据中的一行内容(不包括行尾标记)。
Scanner使用了缓存,所以匹配部分的长度不能超出缓存的容量。默认缓存容量为bufio.MaxScanTokenSize,用户可以通过Buffer方法指定缓存及最大容量
Scan方法 在遇到下面的情况时会终止扫描并返回 false(扫描一旦终止,将无法再继续):
1、遇到 io.EOF
2、遇到读写错误
3、“匹配部分”的长度超过了缓存的长度
结构体
type Scanner struct {
r io.Reader // The reader provided by the client. 由用户提供的io.Reader
split SplitFunc // The function to split the tokens. 用于拆分tokens的函数(匹配函数,默认是行匹配)
maxTokenSize int // Maximum size of a token; modified by tests. token的最大长度
token []byte // Last token returned by split. 记录最后一次Scan操作匹配到的数据
buf []byte // Buffer used as argument to split.
start int // First non-processed byte in buf. 记录缓冲区buf中第一个未处理(扫描过)的位置
end int // End of data in buf. 记录buf的结束位置
err error // Sticky error.
empties int // Count of successive empty tokens. 记录匹配到空数据的次数,如果超过指定maxConsecutiveEmptyReads次数,则会panic
scanCalled bool // Scan has been called; buffer is in use. 标记Scan方法是否已经调用过,如果已经调用过则置为true,说明buffer在正在使用。
done bool // Scan has finished. 标记是否已经扫描结束
}
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
SplitFunc用来定义"匹配函数"类型,data是缓存中的数据,atEOF标记数据是否读完。
advance返回data中已处理的数据的长度。token返回找到的"匹配部分"数据,"匹配部分"可以是缓存的切片,也可以是自己新建的数据(比如bufio.errorRune)。"匹配部分"将在Scan之后通过Bytes和Text反馈给用户。err返回错误信息
如果在 data 中无法找到一个完整的“匹配部分”则应返回 (0, nil, nil),以便告诉Scanner 向缓存中填充更多数据,然后再次扫描(Scan 会自动重新扫描)。如果缓存已经达到最大容量还没有找到,则 Scan 会终止并返回 false。
如果 data 为空,则“匹配函数”将不会被调用,意思是在“匹配函数”中不必考虑data 为空的情况。
如果 err != nil,扫描将终止,如果 err == ErrFinalToken,则 Scan 将返回 true,表示扫描正常结束,如果 err 是其它错误,则 Scan 将返回 false,表示扫描出错。错误信息可以在 Scan 之后通过 Err 方法获取。
SplitFunc 的作用很简单,从 data 中找出你感兴趣的数据,然后返回,同时返回已经处理的数据的长度。
初始化Scanner
func NewScanner(r io.Reader) *Scanner {
return &Scanner{
r: r,
split: ScanLines,
maxTokenSize: MaxScanTokenSize,
}
}
NewScanner函数用于初始化一个Scanner对象,NewScanner返回一个新的Scanner来扫描r,默认匹配函数为ScanLines(行匹配函数)
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的缓冲区大小和最大可扩展的范围(即maxTokenSize),如果max小于len(buf),则buf的尺寸将固定不可调。
Buffer必须在第一次Scan之前设置,否则会引发panic
func (s *Scanner) Split(split SplitFunc) {
if s.scanCalled {
panic("Split called after Scan")
}
s.split = split
}
Split用于设置匹配函数,初始化Scanner的时候默认的匹配函数为ScanLines(行匹配函数),如果想使用其他的匹配函数可以通过该方法设置。
Spit方法也必须在调用Scan前执行,否则会引起panic
获取匹配(Scan)的结果
func (s *Scanner) Err() error {
if s.err == io.EOF {
return nil
}
return s.err
}
该Err方法 返回扫描过程中遇到的非 EOF 错误,供用户调用,以便获取错误信息。
// Bytes returns the most recent token generated by a call to Scan.
// The underlying array may point to data that will be overwritten
// by a subsequent call to Scan. It does no allocation.
func (s *Scanner) Bytes() []byte {
return s.token
}
Bytes方法将最后一次扫描出的“匹配部分”作为一个切片引用返回,下一次的 Scan 操作会覆盖本次引用的内容。
// Text returns the most recent token generated by a call to Scan
// as a newly allocated string holding its bytes.
func (s *Scanner) Text() string {
return string(s.token)
}
Text方法返回最后一次扫描出的"匹配部分"作为字符串返回
核心的匹配方法Scan
func (s *Scanner) Scan() bool {
if s.done {
return false
}
s.scanCalled = true
// Loop until we have a token.
for {
// See if we can get a token with what we already have.
// If we've run out of data but have an error, give the split function
// a chance to recover any remaining, possibly empty token.
if s.end > s.start || s.err != nil { //如果缓冲区的结束位置 > 缓冲区未扫描的位置 或者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
return true
}
s.setErr(err)
return false
}
if !s.advance(advance) {
return false
}
s.token = token //记录本次匹配的token
if token != nil {
if s.err == nil || advance > 0 { //当有匹配的token,并且扫描的长度大于0,则置空empties
s.empties = 0 //emptis是记录连续匹配到为空的次数
} else { //匹配到空数据
// Returning tokens not advancing input at EOF.
s.empties++
if s.empties > maxConsecutiveEmptyReads {
panic("bufio.Scan: too many empty tokens without progressing")
}
}
return true
}
}
// We cannot generate a token with what we are holding.
// If we've already hit EOF or an I/O error, we are done.
if s.err != nil {
// Shut it down.
s.start = 0
s.end = 0
return false
}
// Must read more data.
// First, shift data to beginning of buffer if there's lots of empty space
// or space is needed.
/*
当start> 0 并且 s.end等于整个缓冲区的长度,或者start>0 并且 start大于缓冲区的一半
则将未扫描的部分 移到 缓冲区的开头
*/
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
}
// Is the buffer full? If so, resize.
/*
如果缓冲区已满,
1.如果缓冲区buf的长度已经大于等于maxTokenSize,或者缓冲区buf的长度大于maxInt的一半,则无法扩容
2.将缓冲区扩大到现在缓冲区的2倍和maxTokenSize中的最小者,并设置start和end的值
*/
if s.end == len(s.buf) {
// Guarantee no overflow in the multiplication below.
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
}
if newSize > s.maxTokenSize {
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
}
// Finally we can read some input. Make sure we don't get stuck with
// a misbehaving Reader. Officially we don't need to do this, but let's
// be extra careful: Scanner is for safe, simple jobs.
//从输入流Reader中读取数据到缓冲区中
for loop := 0; ; {
n, err := s.r.Read(s.buf[s.end:len(s.buf)])
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开始一次扫描匹配,如果匹配成功,可以通过Bytes()或Text()方法取出结果,如果遇到错误,则终止扫描,并返回false。
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方法用于Scan操作扫描了n个字节,这里会设置start的索引位置
bufio中内置的匹配函数
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 函数是"字节匹配"函数,扫描下一个字节
func ScanRunes(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
// Fast path 1: ASCII.
if data[0] < utf8.RuneSelf {
return 1, data[0:1], nil
}
// Fast path 2: Correct UTF-8 decode without error.
_, width := utf8.DecodeRune(data)
if width > 1 {
// It's a valid encoding. Width cannot be one for a correctly encoded
// non-ASCII rune.
return width, data[0:width], nil
}
// We know it's an error: we have width==1 and implicitly r==utf8.RuneError.
// Is the error because there wasn't a full rune to be decoded?
// FullRune distinguishes correctly between erroneous and incomplete encodings.
if !atEOF && !utf8.FullRune(data) {
// Incomplete; get more bytes.
return 0, nil, nil
}
// We have a real UTF-8 encoding error. Return a properly encoded error rune
// but advance only one byte. This matches the behavior of a range loop over
// an incorrectly encoded string.
return 1, errorRune, nil
}
ScanRunes函数 是"字符匹配"函数,扫描下一个字符
func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 { //已经扫描结束
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\n'); i >= 0 {//如果缓冲区data中可以找到'\n'换行结束符
// We have a full newline-terminated line.
return i + 1, dropCR(data[0:i]), nil
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF { //如果没有找到换行符,并且atEOF为true,则说明已经EOF,扫描结束,并返回剩余的数据
return len(data), dropCR(data), nil
}
// Request more data.
return 0, nil, nil
}
ScanLines函数是一个"行匹配函数",用来找出data中的单行数据并返回(包括空行),返回值不包含行尾标记(\n或\r\n),advance返回data中已处理的数据长度,该函数也是NewScanner初始化Scanner时默认的匹配函数。
func ScanWords(data []byte, atEOF bool) (advance int, token []byte, err error) {
// Skip leading spaces.
start := 0
for width := 0; start < len(data); start += width {
var r rune
r, width = utf8.DecodeRune(data[start:])
if !isSpace(r) {
break
}
}
// Scan until space, marking end of word.
for width, i := 0, start; i < len(data); i += width {
var r rune
r, width = utf8.DecodeRune(data[i:])
if isSpace(r) {
return i + width, data[start:i], nil
}
}
// If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
if atEOF && len(data) > start {
return len(data), data[start:], nil
}
// Request more data.
return start, nil, nil
}
ScanWords函数是"单词匹配"函数,它返回每个以空格分割的单词,并删除周围的空格。不会返回一个空字符串。