如何在Golang中实现简单的搜索引擎 Go语言倒排索引原理实现

6次阅读

倒排索引构建核心是map[String][]int,将词映射到其出现的文档id升序列表;需小写归一化、去标点、空白分词,文档id用连续整数;and搜索用双指针归并求交,避免嵌套遍历。

如何在Golang中实现简单的搜索引擎 Go语言倒排索引原理实现

倒排索引怎么建:从文档切词到 map[string][]int

核心就一句话:把每个词映射到它出现过的文档 ID 列表。不是存全文,是存「这个词在哪几篇里出现过」。

常见错误是直接用 map[string]string 存原始文本,结果搜“go”时匹配不到“golang”,也做不到按文档 ID 快速取交集。必须先做基础归一化——小写、去标点、简单分词(比如按空白和标点切),再构建 map[string][]int

  • 文档 ID 建议用连续整数(0, 1, 2...),别用文件名或 UUID,后续求交/并集时下标运算快且无哈希开销
  • 切词不用上 full-text 分词器,strings.FieldsFunc(text, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) }) 足够起步
  • 停用词可后期加,初期跳过,避免过早引入维护成本

搜索逻辑怎么写:AND 检索的交集计算不能靠遍历

用户输“golang index”,你要找同时包含这两个词的文档 ID。如果对每个词的结果列表都用 for 嵌套遍历求交,复杂度是 O(n×m),1000 个文档、每个词命中 200 篇时就卡住。

正确做法是双指针归并——两个升序 ID 列表,各一个游标,相等则记录,不等则移动小的那个。Go 标准库没现成函数,但几行就能写完。

立即学习go语言免费学习笔记(深入)”;

  • 确保倒排索引中每个 []int 都是严格递增且无重复(插入时去重 + 排序,或用 sort.Ints 预处理)
  • 多个关键词 AND 检索,不要递归两两合并,用迭代:从第一个词结果开始,逐个与下一个结果求交
  • 如果某次交集结果为空(len(intersection) == 0),立刻返回空,别继续算
func intersect(a, b []int) []int {     i, j := 0, 0     var res []int     for i < len(a) && j < len(b) {         if a[i] == b[j] {             res = append(res, a[i])             i++             j++         } else if a[i] < b[j] {             i++         } else {             j++         }     }     return res }

内存和性能怎么控:别让 map 把进程吃光

倒排索引本质是内存数据结构,文档量一上来,map[string][]int 的 key 字符串和 slice 头都会占不少内存。10 万文档、平均 50 个词,key 就可能上百万个。

容易被忽略的是:Go 的 map 不会自动缩容,删掉大量 key 后内存不释放;而且字符串 key 默认是独立分配的,重复词(比如“the”)哪怕出现一万次,也存一万份地址。

  • sync.Map 不解决内存问题,只解决并发写,读多写少场景反而更慢,优先用普通 map + 读写锁
  • 词典去重:把所有词先塞进 map[string]Struct{},再转成 slice 排序,用 sort.SearchStrings 查词是否存在,能省掉大量重复字符串内存
  • 文档量超 5 万后,考虑把倒排索引拆成两级:一级是词 → block ID,二级是 block 内部偏移,减少单个 slice 长度

为什么搜不到“Go”却能搜到“go”:大小写和归一化必须统一做

这是最常踩的坑:索引时用 strings.ToLower,但搜索时忘了对 query 做同样处理,导致“Go”查不到任何结果。或者反过来,索引没转小写,query 却转了,也匹配不上。

归一化动作必须在唯一入口完成,且对文档和 query 完全一致。别指望前端传来的 query 已经标准化——它可能来自 URL、表单、API body,来源不可控。

  • 归一化函数要封装成独立函数,比如 normalizeText,索引构建和查询解析都调它,别复制粘贴逻辑
  • 别用正则替换 Unicode 字母大小写(regexp.MustCompile(`[A-Z]`)),用 strings.Map(unicode.ToLower, text) 更稳妥
  • 测试用例至少覆盖:空格前后、标点夹着、首字母大写、全大写、带数字混合(如 “Go2”)

倒排索引本身很简单,难的是边界——词怎么切、ID 怎么编、内存怎么扛、大小写怎么对齐。这些地方一松动,搜出来的东西就不可信。

text=ZqhQzanResources