go标准库无全文索引,需自建倒排索引;strings.contains仅支持子串匹配,不支持词干、布尔查询、排序等,且全量扫描性能差;须预处理(小写、分词)、内存存储词→文档id映射。

Go 标准库不提供现成的全文索引能力,必须组合使用字符串处理、倒排索引结构和轻量级分词逻辑;直接上 bleve 或 elasticsearch 是过度设计,而纯正则遍历是性能黑洞。
为什么不能直接用 strings.Contains 做全文搜索
它只支持子串匹配,无法处理“搜索 running 却命中 run”这类词干匹配,也不支持多字段加权、布尔组合(AND/OR)、排序或高亮。更关键的是——它每次都要扫完整个文档集,数据量超 1000 条后延迟就明显可见。
常见错误现象:Search("golang tutorial") 返回空,但文档里有 “Go language tutorial” —— 因为没做标准化(大小写、同义词、空格归一)和分词。
- 必须先对文档和查询都做统一预处理:小写化、去标点、基础分词(按空白或 Unicode 字符边界)
- 不能依赖
strings.Fields处理中文;需用gojieba或segment库(若含中文) - 避免在搜索时实时分词;应在索引构建阶段完成并存入内存结构
用 map[string][]int 实现最简倒排索引
核心就是把“词 → 文档 ID 列表”存进内存 map。适合中小规模(
示例结构:
type InMemoryIndex struct {
documents []string
// term → list of doc IDs containing it
inverted map[string][]int
}
构建索引时的关键动作:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 对每个文档
documents[i]调用自定义分词函数(如tokenize(doc)),返回[]string - 对每个 token,执行
index.inverted[token] = append(index.inverted[token], i) - 务必去重:同一文档内重复词只记一次 ID,否则影响 TF 计算和结果去重
- 若需支持短语搜索(如
"fast http"),还需额外存储每个词在文档内的位置(用map[string][][2]int,第二维存[docID, position])
如何支持 AND/OR 查询和排序
纯 map 查找只能返回“包含某词”的文档 ID 列表;要实现 golang AND tutorial,就得对多个词的结果做交集;OR 则是并集。交集用双指针比 map 查找更快,尤其当列表已排序(ID 天然有序)。
实操建议:
- 用
sort.IntersectionInts(来自golang.org/x/exp/slices)做 AND;它要求输入切片已升序,且复杂度是 O(m+n) - OR 查询直接
append后去重(slices.Compact+sort.Ints) - 排序靠 TF-IDF 粗略打分:TF = 该词在文档中出现次数(构建索引时额外记录),IDF =
log(N / len(inverted[token]));N 是总文档数 - 避免在每次查询时重算 IDF —— 它只随文档总数变化,可缓存在结构体里
中文分词与内存占用容易被忽略的点
用 strings.Fields 处理中文会把整句当一个 token,导致零召回。必须引入分词器,但 gojieba 默认加载词典约占用 8MB 内存,且首次分词有毫秒级延迟。
真实项目中踩过的坑:
- 没调用
jieba.NewJieba()就直接Cut,panic 报错nil pointer dereference - 把分词结果全转小写(
strings.ToLower)导致中文乱码 —— 中文不需要大小写转换,应只对 ASCII 字母做 - 倒排索引 key 用了原始 token(含空格/标点),导致查询
"api"匹配不到"api."—— 预处理阶段必须清洗 token(strings.TrimSpace+ 正则[\p{P}]+) - 文档量到 5 万条时,
invertedmap 占用超 200MB —— 可改用sync.Map分片(按首字母 hash 到 26 个子 map),降低 GC 压力
真正上线前,用 runtime.ReadMemStats 对比索引前后内存增长,比任何文档都管用。










