map[string][]int是起点但非全部,需满足文档id连续整数、重复词项保留多次id、每个[]int显式排序去重;中文分词必须用gse并关闭词频;and查询须用双指针归并而非map求交。

map[string][]int 是起点,但不是全部
直接用 map[string][]int 存词项到文档 ID 的映射,确实能跑通基础流程,但一加真实数据就出问题:搜不到、结果错、内存涨得快。根本原因不在结构本身,而在构建逻辑没对齐三个刚性约束:
- 文档 ID 必须是连续整数(0, 1, 2…),不能是文件路径哈希或 UUID——双指针归并依赖严格递增且无跳变,否则
intersect会漏匹配 - 同一文档中重复出现的词,必须保留多次 ID(如文档 123 中 “error” 出现 5 次,就要
append5 次 123)——TF 计算和高亮定位都靠它 - 每个
[]int必须在插入后显式排序且去重(或插入时用sort.SearchInts查重再插)——否则双指针归并失效,退化成嵌套遍历
中文分词别用 strings.Fields,gse 要关词频
英文还能靠 strings.FieldsFunc(text, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) }) 应急,中文必须上 github.com/go-ego/gse。但默认配置会拖慢索引构建、撑大内存:
- 必须传
seg.WithFrequency(false)——倒排索引只关心“是否出现”,不需要每个词的出现次数,关掉词频能省 30%+ 内存 - 分词后立刻过滤单字和停用词:
if len(token.Token) > 1 && !stopWords[token.Token],否则 “的”、“了”、“在” 大量进索引,查什么都是满屏无关结果 - 别用
strings.Split或正则切中文——它按字切,把 “搜索引擎” 切成 [“搜”, “索”, “引”, “擎”],完全失去语义
AND 查询必须用双指针,别碰 map[int]bool
用户搜 “golang 内存”,你要返回同时含这两个词的文档 ID。常见错误是把 index["golang"] 和 index["内存"] 转成 map[int]bool 再遍历求交——这不仅多占一倍内存,还破坏 CPU 缓存局部性:
- 输入两个已排序、无重复的
[]int(比如[1,3,5,7]和[3,4,5,8]) - 双指针逻辑只比较大小:
if a[i] == b[j]就记录,a[i] 就 <code>i++,反之j++ - 返回结果仍是递增
[]int,方便后续再和其他词求交,不额外排序
bleve 不是“重”,是省掉你踩坑的成本
手写倒排索引适合 CLI 工具、嵌入式设备或教学验证,但一旦要支持中文分词、字段权重、模糊查询、增量更新,几乎必然掉坑里。bleve 看似多一层依赖,实则封死了最常崩的点:
-
bleve.New()前必须os.MkdirAll("/path/to/index", 0755),相对路径(如"./index")在 CI 或不同工作目录下必挂 - 字段必须显式设
field.Index(true).Store(true),仅靠json:"title"标签无效 - 中文字段必须绑定分词器:
mapping.AddCustomAnalyzer("jieba", analyzer)+titleField.Analyzer = "jieba",漏掉就搜不到
真正难的不是写代码,是让每行索引数据都落在正确的位置、每次查询都命中预期的 offset——这些细节藏在 scanner.Seek、delta 编码、fst 构建里,bleve 已经替你压平了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











