分享
  1. 首页
  2. 文章

Go Scanner的使用和源码分析

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

简介

go标准库bufio.Scanner,从字面意思来看是一个扫描器、扫描仪。 所用是不停的从一个reader中读取数据兵缓存在内存中,还提供了一个注入函数用来自定义分割符。库中还提供了4个预定义分割方法。

  • ScanLines:以换行符分割('n')
  • ScanWords:返回通过"空格"分词的单词
  • ScanRunes:返回单个 UTF-8 编码的 rune 作为一个 token
  • ScanBytes:返回单个字节作为一个 token

使用方法

在看使用方法之前,我们需要先看一个函数。

type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
这个函数接受一个byte数组,和一个atEOF标志位(标志位用来表示是否还有更多的数据)返回的是3个返回值。第一个是推进输入的字节(一般为标志位字节数)
在splist函数判断是否找到标志位,如果没有找到则可以返回(0,nil,nil) Scan获取到这个返回值则会继续读取之后未读取完成的字符。如果找到则按照正确的返回值返回。下面是一个简单的使用例子
func main() {
 input := "abcend234234234"
 fmt.Println(strings.Index(input,"end"))
 scanner := bufio.NewScanner(strings.NewReader(input))
 scanner.Split(ScanEnd)
 //设置读取缓冲读取大小 每次读取2个字节 如果缓冲区不够则翻倍增加缓冲区大小
 buf := make([]byte, 2)
 scanner.Buffer(buf, bufio.MaxScanTokenSize)
 for scanner.Scan() {
 fmt.Println("output:",scanner.Text())
 }
 if scanner.Err() != nil {
 fmt.Printf("error: %s\n", scanner.Err())
 }
}
func ScanEnd(data []byte, atEOF bool) (advance int, token []byte, err error) {
 //如果数据为空,数据已经读完直接返回
 if atEOF && len(data) == 0 {
 return 0, nil, nil
 }
 // 获取自定义的结束标志位的位置
 index:= strings.Index(string(data),"end")
 if index > 0{
 //如果找到 返回的第一个参数为后推的字符长度 
 //第二个参数则指标志位之前的字符 
 //第三个参数为是否有错误
 return index+3, data[0:index],nil
 }
 if atEOF {
 return len(data), data, nil
 }
 //如果没有找到则返回0,nil,nil
 return 0, nil, nil
}

上面的例子可以看到 字符串是"abcend234234234"
因为设置的是每次读取2个字符串
第一次读取: buf = ab 没有找到end ScanEnd返回 0,nil,nil
第二次读取: buf = abce 没有找到end ScanEnd返回 0,nil,nil
第三次读取: buf = abcend23(buf翻倍扩容) 找到自定义标志位end 返回:6,abc, nil 打出 out abc
第四次读取: buf = 23423423 之前的已经读取的被去掉,犹豫buf大小为8 直接读取8个字符
第五次读取: 由于buf容量不足翻倍之后 直接获取全部数据输出 out 234234234
结果则是:
output: abc
output: 234234234
可以看到 扫描器 按照自定义的读取大小和结束符token 输出结果

源码查看

type Scanner struct {
 r io.Reader // reader
 split SplitFunc // 分割函数 又外部注入
 maxTokenSize int // token最大长度
 token []byte // split返回的最后一个令牌
 buf []byte // 缓冲区字符
 start int // buf中的第一个未处理字节
 end int // buf中的数据结束 标志位
 err error // Sticky error.
 empties int // 连续空令牌的计数
 scanCalled bool //
 done bool // 扫描是否完成
}
func (s *Scanner) Scan() bool {
 if s.done {
 return false
 }
 s.scanCalled = true
 // for循环知道找到token为止
 for {
 if s.end > s.start || s.err != nil {
 // 调用split函数 得到返回值,函数中判断是否有token token往后推的标志位数 是否有错误
 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 {
 // Returning tokens not advancing input at EOF.
 s.empties++
 if s.empties > 100 {
 panic("bufio.Scan: 100 empty tokens without progressing")
 }
 }
 return true
 }
 }
 //如果有错误 则返回false
 if s.err != nil {
 // Shut it down.
 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
 }
 // 如果buf满了 如果满了重新创建一个长度为原来两倍大小的buf
 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
 }
 //如果没有找到则往后继续读取数据
 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
 }
 }
 }
}

总结

根据上面的源码和例子可以看到这个扫描器的作用,当然正式使用时候不会只是读取一个写死的字符串。可以使用在读取scoket读取数据,IO 缓冲区 提供了一个临时存储区来存放数据,缓冲区存储的数据达到一定容量后才会被"释放"出来进行下一步存储,这种方式大大减少了写操作或是最终的系统调用被触发的次数,这无疑会在频繁使用系统资源的时候节省下巨大的系统开销。而对于读操作来说,缓冲 IO 意味着每次操作能够读取更多的数据,既减少了系统调用的次数,又通过以块为单位读取硬盘数据来更高效地使用底层硬件。


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

本文来自:Segmentfault

感谢作者:大二小的宝

查看原文:Go Scanner的使用和源码分析

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

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

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏