如何优化基于bufio.NewScanner的脚本读取Go程序性能
优化方案
你认为scanner.Bytes()不适用的担心是多余的,strings.Builder原生支持写入字节切片,全程使用字节操作仅在最后调用.String()时做一次类型转换,反而能省掉每次循环中scanner.Text()产生的字符串拷贝开销,核心优化点如下:
- 替换
scanner.Text()为scanner.Bytes(),所有行处理逻辑直接基于字节切片完成,避免每次循环的字符串内存拷贝开销。 - 移除行拼接逻辑:原代码中
lines + "; "会生成临时字符串产生额外拷贝,改为分两次写入行内容和固定后缀,消除临时对象开销。 - 预分配
strings.Builder缓冲区:提前获取文件大小,调用.Grow()方法预留足够的内存空间,避免运行时动态扩容带来的多次内存拷贝。 - 优化判断逻辑:空行直接判断字节切片长度是否为0,注释行直接判断切片首字节是否为
#,行尾判断直接取字节切片最后一个字符对比,比字符串操作效率更高。 - 新增
scanner.Err()错误处理,补全原代码缺失的读取错误捕获逻辑。
优化后的代码如下:
package main import ( "bufio" "fmt" "log" "os" "strings" ) func main() { filePath := "./script.sh" file, err := os.Open(filePath) if err != nil { log.Fatalln(err) } defer file.Close() // 获取文件大小用于预分配缓冲区 fileInfo, err := file.Stat() if err != nil { log.Fatalln(err) } var builder strings.Builder // 预留略大于文件的空间,覆盖添加的分号和空格开销,避免扩容 builder.Grow(int(fileInfo.Size() + 1024)) scanner := bufio.NewScanner(file) // 预定义后缀字节切片,避免重复生成 semicolonSuffix := []byte("; ") spaceSuffix := []byte(" ") for scanner.Scan() { lineBytes := scanner.Bytes() switch { case len(lineBytes) == 0 || lineBytes[0] == '#': continue case lineBytes[len(lineBytes)-1] != ';': builder.Write(lineBytes) builder.Write(semicolonSuffix) default: builder.Write(lineBytes) builder.Write(spaceSuffix) } } // 处理扫描过程中可能出现的错误 if err := scanner.Err(); err != nil { log.Fatalln(err) } fmt.Println(builder.String()) }
以上优化在处理KB级以上的脚本文件时会有明显的性能提升,文件越大优化效果越显著。
内容的提问来源于stack exchange,提问作者Adam Woolhether
相关产品推荐
相关产品推荐

