strings.Contains + 内存遍历或bleve适用于几百至几千条固定字段数据的简单关键词搜索;手写倒排索引只需词归一化、ID映射、双指针求交,ID须为连续整数,切词用FieldsFunc,词项列表须递增无重复,AND搜索必须用双指针归并。
别从零写全文检索——99% 的场景下,strings.contains + 内存遍历或 bleve 就够用;真要手写倒排索引,核心就三件事:词归一化、id 映射、双指针求交,其余全是坑。
什么时候该放弃手写,直接用 strings.Contains
数据量在几百到几千条、字段固定(比如博客文章切片)、搜索逻辑只是“标题或内容里包含关键词”,这时候硬上倒排索引纯属自找麻烦。
- 用
strings.EqualFold替代反复strings.ToLower,对非 ASCII 字符(如café)更准,且不分配新字符串 - 拼接字段再查比两次
Contains快:text := a.Title + "\n" + a.Content,然后一次strings.Contains(text, keyword) - 关键词带空格想实现 AND?拆成
strings.Fields(keyword),循环检查每个词是否都strings.Contains,比正则轻量可控 - 别在搜索循环里做
strings.ToLower(a.Title)——提前转好存进结构体字段,避免重复分配
真要手写倒排索引,必须守住三个边界
倒排索引本质是 map[string][]int,但错一个环节,搜索就静默失败或性能崩盘。
- 文档 ID 必须是连续整数(0, 1, 2…),不是文件名、UUID 或路径——后续双指针归并依赖升序无跳变,哈希或字符串 ID 会让交集计算退化成 O(n²)
- 切词不能靠
strings.Split或正则:用strings.FieldsFunc(text, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) }),干净利落去标点、按字符类别切分 - 每个词对应的
[]int必须严格递增且无重复——插入时用sort.SearchInts查重 + 插入,或每次插入后sort.Ints预处理,否则双指针会漏匹配
AND 搜索必须用双指针归并,别嵌套 for
用户搜 “golang index”,你要找同时含这两个词的文档 ID。如果对每个词的结果列表都嵌套遍历,1000 篇文档、每个词命中 200 篇时,比较次数轻松破 4 万次。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 双指针写法极简:
i, j := 0, 0; for i - 多个关键词 AND,别递归两两合并——用迭代:从第一个词结果开始,逐个与下一个结果求交;某次交集为空(
len(intersection) == 0),立刻返回,不继续算 - 确保所有输入列表已排序且无重复,否则双指针逻辑失效;手写时加个
debug.Assert(sort.IsSorted(sort.IntSlice(list)))防御性检查
bleve 不是“重”,是省掉你踩坑的成本
当你开始纠结“要不要支持模糊查询”“中文怎么分词”“索引更新后搜不到”“并发写 panic”,说明已经掉进手写的深坑——bleve 不是重量级方案,而是把所有这些细节封装成可调用的接口。
立即学习“go语言免费学习笔记(深入)”;
-
bleve.New()路径必须是空目录或不存在,已有索引请用bleve.Open();权限错误先os.MkdirAll(path, 0755) - 字段不显式设
Index(true)+Store(true),文档存进去了也搜不到;中文必须注册gojieba并在 mapping 中指定 analyzer - 搜索别手拼 JSON DSL:
bleve.NewQueryStringQuery("title:go AND body:web")自动处理语法,比map[string]interface{}少 80% 类型错误 - 并发写入必须加锁或串行化——
Index.Index()不是线程安全的,多个 goroutine 直接调用会触发concurrent map read and map write
真正难的不是写倒排索引,而是让大小写、编码、停用词、同义词、增量更新、崩溃恢复全都不出错;这些事 bleve 做了十年,而你重写一遍,大概率卡在某个 UTF-8 解码 panic 或模糊距离算错上。


















