Jun 21

golang: bufio.Scanner 的坑 不指定

felix021 @ 2020-6-21 01:03 [IT » 程序设计] 评论(0) , 引用(0) , 阅读(1942) | Via 本站原创 | |
之前从网上找的一段代码,按行读取文件:

inFile, err := os.Open("xxx.log")
if err != nil {
    fmt.Fprintf(os.Stderr, "open failed: %v\n", err)
    return
}
defer inFile.Close()

scanner := bufio.NewScanner(inFile)
for scanner.Scan() {
    line := scanner.Bytes()
    //do sth. with line
}


看起来没问题,用起来也没问题,直到踩了个坑:针对某个特定的文件,读取到某一行以后就不再继续了。

既然总能复现,那就好解决,我的一个常用方法是:制造一个总能复现的case,并不断缩小case的规模。

例如这个case,把那一行单独拿出来,通过二分找到出问题的位置。

原以为是该行有特殊字符导致触发了什么奇怪的逻辑,但经过不断尝试,发现临界点是该行长度 = 65536 的时候,正好会触发错误。

这么整的数字(2^16, 64KB)必然是代码里的特殊逻辑了,翻了一下 bufio 的源码,果然有一个

const (
  //...(一堆注释)...
  MaxScanTokenSize = 64 * 1024
)


搜索这个常量在代码里的引用:
func NewScanner(r io.Reader) *Scanner {
  return &Scanner{
    r:            r,
    split:        ScanLines,
    maxTokenSize: MaxScanTokenSize,
  }
}

...

func (s *Scanner) Scan() bool {
  ....
  if len(s.buf) >= s.maxTokenSize || len(s.buf) > maxInt/2 {
    s.setErr(ErrTooLong)
    return false
  }
  ...
}


在 for 循环后加上一句:
if scanner.Err() != nil {
  fmt.Fprintf(os.Stderr, "scan err: %v\n", scanner.Err())
}


实锤:
引用
scan err: bufio.Scanner: token too long



那怎么解决呢?

MaxScanTokenSize 上面的注释是这么写的:

引用
  // MaxScanTokenSize is the maximum size used to buffer a token
  // unless the user provides an explicit buffer with Scanner.Buffer.
  // The actual maximum token size may be smaller as the buffer
  // may need to include, for instance, a newline.


于是最终版的解决方案是这样:

...
scanner := bufio.NewScanner(inFile)
buf := make([]byte, 0, bufio.MaxScanTokenSize * 10) //根据自己的需要调整这个倍数
scanner.Buffer(buf, cap(buf))

for scanner.Scan() {
  line := scanner.Bytes()
  //do sth. with line
}

if scanner.Err() != nil {
  fmt.Fprintf(os.Stderr, "scan err: %v\n", scanner.Err())
}


真是丑陋的api啊。



欢迎扫码关注:




转载请注明出自 ,如是转载文则注明原出处,谢谢:)
RSS订阅地址: https://www.felix021.com/blog/feed.php
发表评论
表情
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
打开HTML
打开UBB
打开表情
隐藏
记住我
昵称   密码   *非必须
网址   电邮   [注册]