DjanFey的基础库解读--bufio包(scan.go)

DjanFy · · 612 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

package my_bufio

import (
   "bytes"
   "errors"
   "io"
   "unicode/utf8"
)

// Scanner为读取数据操作(例如文本用换行符分隔行的文件)提供了一个便利的接口
// 成功调用Scan方法会逐条扫描文件的'token',跳过'token'之间的字节
// token的规范是由SplitFunc类型的分割函数定义的
// 默认的分割函数是把输入按行终止符分割成多行, 去掉了行终止符
// 本包中定义了各种分割函数,用来按行、字节、UTF-8编码的字符以及空格来扫描一个文件
// 客户端也可以提供一个自定义的分割函数来代替
//
// 当在EOF、第一个I/O错误、或者一个'token'大到超过缓冲区时,扫描动作就会不可逆转地停止
// 当一个扫描停止时,reader可能比上一个'token'前进任意字节
// 程序需要更加注意错误的处理和过大'token'的处理,或者当必须串行化在reader上执行扫描时,应该使用bufio.Reader来代替
type Scanner struct {
   r io.Reader // 客户端提供的Reader
   split SplitFunc // 用来分割成'token'的函数
   maxTokenSize int // 一个'token'的最大字节数
   token []byte // split上一次返回的'token'
   buf []byte // buffer作为分割的参数
   start int // buf中第一个未处理的字节
   end int // buf中数据的结尾位置
   err error // 附加的错误
   empties int // 连续出现空'token'的次数
   scanCalled bool // Scan已经被调用,缓冲区也正在被使用
   done bool // Scan已经完成
}

// SplitFunc是一个分割函数的签名,用来将输入分词
// 参数是这个未处理数据的一个字句和一个atEOF标志(用来报告Reader是否还有更多数据可读)
// 返回的结果是输入数据的前进字节数和下一个将要返回给用户的'token'(如果有的话), 以及一个错误(如果有的话)
//
// 扫描动作在返回一个错误的情况下将会停止,在这种情况下一些输入将会被丢弃
//
// 否则的话在输入上的扫描动作继续前进,如果'token'不是nil,那么就返回给用户。如果'token'是nil,那么Scanner会读取更多数据并继续扫描
// 如果没有更多的数据了(atEOF为true),这个Scanner就会返回。如果数据持有的不是一个完整的'token',例如当扫描行的时候没有新行,那么这个SplitFunc可以返回0,nil,ni
// 来指示Scanner读取更多的数据到slice并且用一个长一点的slice在输入的相同点再次尝试
//
// 一个空的数据slice上此方法永远不会被调用,除非atEOF为true。
// 如果atEOF为true,然而数据可能不是空的,那么按照惯例会持有未处理的文本
type SplitFunc func(data []byte, atEOF bool)(advance int,token []byte,err error)

// Scanner返回的错误
var (
   ErrTooLong = errors.New("bufio.Scanner: token too long") // 错误:token太长了
   ErrNegativeAdvance= errors.New("bufio.Scanner: SplitFunc returns negative advance count") // 错误:SplitFunc返回负前进数
   ErrAdvanceTooFar = errors.New("bufio.Scanner: SplitFunc returns advance count beyond input") // 错误:SplitFunc返回的前进数超过了输入的大小
)

const (
   // MaxScanTokenSize是用来缓冲一个'token'的最大大小,除非用户用Scanner.Buffer来显示地提供一个buffer
   // 当buffer需要包含的时候,例如一个新行,实际的token最大尺寸可能会小一些
   MaxScanTokenSize =64*1024
   startBufSize = 4096 // 为buffer初始配置的大小
)

// NewScanner返回一个新的Scanner去从r中读取数据
// 默认的分割函数是ScanLines
func NewScanner(r io.Reader) *Scanner {
   return &Scanner{
      r:            r,
      split:        ScanLines,
      maxTokenSize: MaxScanTokenSize,
   }
}

// Err返回Scanner遇到的第一个非EOF错误
func (s *Scanner) Err() error {
   if s.err==io.EOF{
      return nil
   }
   return s.err
}

// Bytes返回最近调用Scan生成的token
// 底层指向数据的数组在下一次调用Scan后将会被覆写。它不会分配新的数组。
func (s *Scanner) Bytes() []byte {
   return s.token
}

// Text返回最近调用Scan生成的token转化成的字符串
func (s *Scanner) Text() string {
   return string(s.token)
}

// ErrFinalToken 是一个特殊的哨兵错误值。
// 它倾向于被一个分割函数返回来表示带着这个错误传递的token是最后一个token,并且在这个token后,扫描动作应该停止
// ErrFinalToken被Scan接收后,扫描动作会正常停止,没有错误。
// 这个值用来提前停止处理或者当有必要去传递一个最终的空token
// 用一个自定义的错误值也可以获得相同的效果,但是使用这个错误就更加规范
// emptyFinalToken就是使用这个值得例子
var ErrFinalToken = errors.New("final token")

// Scan将Scanner前进到下一个token,这个token可以通过Bytes和Text方法得到
// 当Scan停止,或者扫描到了输入的尾部,或者遇到一个错误,都会返回false
// 当Scan停止后,Err方法会返回遇到的任何错误,除了EOF,EOF错误会返回nil
func (s *Scanner) Scan() bool {
   if s.done {
      return false
   }
   s.scanCalled=true
   // 一直循环直到扫描到token
   for {
      // 用我们已持有的看能否得到一个token
      // 如果我们消耗光了所有的数据也没有错误,要给split函数一个机会去恢复任何残留工作,可能是空token
      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
               return true
            }
            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时返回tokens而不前进输入
               s.empties++
               if s.empties>maxConsecutiveEmptyReads{
                  panic("bufio.Scan: too many empty tokens without progressing")
               }
            }
            return true
         }
      }
      // 当我们已持有的不能生成一个token
      // 如果我们已经触发了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
      }
      // buffer是否已经满了?如果是,则resize
      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
         }
         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
      }
      // 最后我们读取一些输入
      // 确保我们没有陷入一个行为不当的Reader
      // 正常情况下我们不需要这样做,但是让我们更加小心一些,因为Scanner是为了安全,简单的工作
      for loop:=0;;{
         n,err:=s.r.Read(s.buf[s.end:])
         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
         }
      }
   }
}

// 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
}

// setErr记录第一次遇到的错误
func (s *Scanner) setErr(err error) {
   if s.err==nil || s.err==io.EOF{
      s.err=err
   }
}

// 在扫描的时候初始化buffer来使用,并且可能设置最大尺寸的buffer

func (s *Scanner) Buffer(buf []byte, max int) {

}

// Split给Scanner设置split函数
// 默认的split函数是ScanLines
//
// 如果扫描已经开始后调用Split将会panic
func (s *Scanner) Split(split SplitFunc) {

}


// dropCR从data中丢掉终止符'\r'
func dropCR(data []byte) []byte {
   if len(data)>0&&data[len(data)-1]=='\r'{
      return data[:len(data)-1]
   }
   return data
}

// ScanLines是Scanner的一个分割函数,用来返回文本的每一行,并去掉了行尾标记
// 返回的行可能是空行
// 行尾标记是一个可选的回车('\r')后跟一个强制的换行('\n')。在正则表达式中的概念为`\r?\n`
// 输入的最后一个非空行将会被返回即使它没有行结尾标志
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{
      // 我们有了一个不带行终止符的新行
      return i+1,dropCR(data[:i]),nil
   }
   // 如果atEOF为true,表示我们有了最后一行,直接返回
   if atEOF{
      return len(data),dropCR(data),nil
   }
   return 0,nil,nil
}

// isSpace报告是否这个字符是unicode编码的空格字符
// 我们避免依赖unicode包去实现合法性检查
func isSpace(r rune) bool {
   if r<='\u00FF'{
      // ASCII部分:\t \r 和 空格 以及 两个古怪的字符
      switch r {
      case ' ', '\r', '\t', '\n', '\v', '\f':
         return true
      case '\u0085', '\u00A0':
         return true
      }
   }
   // 高值部分
   if '\u2000'<=r&&r<='\u200a'{
      return true
   }
   switch r {
   case '\u1680', '\u2080', '\u2029', '\u202f', '\u205f', '\u3000':
      return true
   }
   return false
}

// ScanWords是Scanner的一个分割函数,返回文本中以空格分割并去掉空格的单词。
// 它永远不会返回空的字符串
// 空格是按照unicode.IsSpace来定义的
func ScanWords(data []byte, atEOF bool) (advance int, token []byte, err error) {
   // 跳过开头的所有空格
   start:=0
   for width:=0;start<len(data);start+=width{
      var r rune
      r,width=utf8.DecodeRune(data[start:])
      if !isSpace(r){
         break
      }
   }
   // 一直扫描直到遇到空格,因为空格标志着单词的结尾
   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
      }
   }
   // 请求更多数据
   return start,nil,nil
}

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

612 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传