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 }
有疑问加站长微信联系(非本文作者)