标题:golang: bufio.Scanner 的坑 出处:Felix021 时间:Sun, 21 Jun 2020 01:03:54 +0000 作者:felix021 地址:https://www.felix021.com/blog/read.php?2222 内容: 之前从网上找的一段代码,按行读取文件: 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啊。 Generated by Bo-blog 2.1.0