Golang bufio包Scanner源码

Go jdk版本1.10.3  Scanner源码在bufio包中的scan.go文件中

 

一、Scanner介绍

       Scanner提供一个按指定规则来读取数据的缓冲区IO,比如通过”行匹配函数”来逐行读取数据,Scanner通过Scan方法来按”匹配函数”读取数。

      Scan方法会通过一个“匹配函数”读取数据中符合要求的部分,跳过不符合要求的部分。

      “匹配函数”由调用者指定。本包中提供的匹配函数有"行匹配函数"、"字节匹配函数"、"字符匹配函数"、"单词匹配函数",用户也可以自定义"匹配函数"。默认的匹配函数为"行匹配函数",用于获取数据中的一行内容(不包括行尾标记)。

      Scanner使用了缓存,所以匹配部分的长度不能超出缓存的容量。默认缓存容量为bufio.MaxScanTokenSize,用户可以通过Buffer方法指定缓存及最大容量

 

Scan方法 在遇到下面的情况时会终止扫描并返回 false(扫描一旦终止,将无法再继续):

1、遇到 io.EOF

2、遇到读写错误

3、“匹配部分”的长度超过了缓存的长度

 

二、Scanner源码

结构体

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函数是"单词匹配"函数,它返回每个以空格分割的单词,并删除周围的空格。不会返回一个空字符串。

 

你可能感兴趣的:(Golang源码)