聚合国内IT技术精华文章,分享IT技术精华,帮助IT从业人士成长

golang: bufio.Scanner 的坑

2020-06-21 02:15 浏览: 1247 次 我要评论(0 条) 字号:

之前从网上找的一段代码,按行读取文件:

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啊。


网友评论已有0条评论, 我也要评论

发表评论

*

* (保密)

Ctrl+Enter 快捷回复