SuffixArray 适合对固定文本高频查询不同子串,预处理后每次查询仅需 O(m log n),但一次性搜索反而更慢;其核心优势是复用性而非单次速度。

为什么 SuffixArray 比 strings.Index 更适合高频子串搜索
单次查找,strings.Index 更快;但若对同一文本反复查不同子串(比如日志分析、语法高亮预处理),构建一次 SuffixArray 后每次查询可压到 O(m log n)(m 是子串长,n 是原文长),远优于暴力扫描的 O(n)。关键在于:它把“查什么”从运行时移到了预处理阶段。
常见错误是拿 SuffixArray 去做一次性搜索——反而因建树开销(O(n log n))更慢。只在重复查、文本不变、子串变的场景才值得用。
- 适用场景:
logText固定,需查上百个关键词(如"error"、"timeout"、"status=500") - 不适用场景:HTTP 请求体每次不同,只查 1 次
"Authorization" - 注意:
suffixarray.New接收[]byte,不是string;传string会隐式转码,但别依赖它——显式用[]byte(s)
SuffixArray.Search 返回的是位置,不是布尔值
它返回匹配起始下标的切片([]int),空切片表示没找到。很多人误以为返回 bool 或单个 int,结果直接用 if sa.Search(...) 导致编译失败或逻辑错。
示例:
立即学习“go语言免费学习笔记(深入)”;
sa := suffixarray.New([]byte("ababab"))
positions := sa.Search([]byte("aba")) // 返回 []int{0, 2, 4}
// 不是 if sa.Search(...) { ... },也不是 pos := sa.Search(...)[0]
- 查是否存在?用
len(positions) > 0 - 只取第一个匹配?
if len(positions) > 0 { first := positions[0] } - 子串本身含
\x00?SuffixArray支持,但Search输入必须是合法[]byte,不会帮你截断或报错
内存与构建耗时:别在请求中实时建 SuffixArray
SuffixArray 构建过程占内存约 20× 原文长度(Go 1.21),且耗时敏感于文本分布。对 10MB 日志建树可能卡住 HTTP handler 几百毫秒。
- 正确做法:启动时预构建,存在全局变量或缓存里(
var logSA = suffixarray.New(logBytes)) - 文本动态增长?不能增量更新,得重建——考虑分块(如按小时切日志,每块独立建 SA)
- 小文本(strings.Index,直接别用
- 注意:
SuffixArray不是线程安全的,多 goroutine 并发查没问题,但边查边重建会 panic
替代方案:什么时候该放弃 SuffixArray 改用 ahocorasick 或正则
如果你要查的是一组固定关键词(而非任意子串),且数量较多(>10 个),ahocorasick 库一次扫描就能匹配全部,比循环调 Search 快得多。而如果子串带通配或需上下文(如 “return.*error”),正则更合适——SuffixArray 只支持精确字面匹配。
- 用
github.com/BobuSumisu/ahocorasick:构建 AC 自动机,O(n + m + z)总耗时(z 是匹配总数) - 用
regexp:支持模式,但编译开销大,别在循环里regexp.Compile -
SuffixArray的不可替代性:需要所有出现位置、支持二分定位、后续要做 LCP 分析等——这时绕不开它
真正容易被忽略的是:SuffixArray 的优势不在“快”,而在“可复用性”。建一次,查千次,才摊薄成本。否则就是杀鸡用牛刀,还嫌刀沉。










