热词排行是带权重的词频统计与多维排序,非搜索功能;Golang用map[string]int统计、sort.Slice多级排序即可满足90%场景。需字段加权(标题×3、正文×1)、统一小写、清洗字符、过滤无效词,并在统计时记录首次位置以支持四层排序。

直接上结论:热词排行本质是「带权重的词频统计 + 多维排序」,不是搜索功能本身,别误用全文索引或倒排索引;Golang 用 map[string]int 统计频次、sort.Slice 控制多级排序规则,就能稳住 90% 的内部舆情/日志热词场景。
热词统计前必须做字段加权归一化
热词不是简单切词计数——标题和正文语义权重不同,原始字符串也不干净。不处理会导致“安装”和“安装?”、“Golang”和“golang”被算作两个词,排名失真。
- 标题词频 × 3,正文词频 × 1(题目明确要求,也是通用实践)
- 统一转小写:
strings.ToLower(word),避免大小写分裂 - 过滤非字母数字字符:
strings.Map(func(r rune) rune { if unicode.IsLetter(r) || unicode.IsNumber(r) { return r } else { return -1 } }, word) - 跳过长度 ≤ 1 的词(如 “a”、“I”),也跳过纯数字(除非业务需要)
用 map[string]int 累加频次比 slice 遍历快得多
有人试图对每篇文章跑一遍 strings.Fields 再遍历切片去查 map 是否存在,这是冗余操作。正确做法是一次性提取所有有效词,直接累加:
freq := make(map[string]int)
for _, article := range articles {
// 标题处理
for _, word := range strings.Fields(strings.ToLower(article.Title)) {
clean := cleanWord(word) // 上面定义的清洗函数
if len(clean) > 1 {
freq[clean] += 3 // 标题权重
}
}
// 正文处理
for _, word := range strings.Fields(strings.ToLower(article.Content)) {
clean := cleanWord(word)
if len(clean) > 1 {
freq[clean] += 1
}
}
}
注意:map 并发写要加锁,但热词统计通常是批处理(如定时任务拉取 M 篇文章后单次计算),无需 sync.Map,普通 map + 单 goroutine 更轻量。
立即学习“go语言免费学习笔记(深入)”;
多条件排序必须用 sort.Slice,不能只靠 sort.Strings
题目要求排序优先级:词频降序 → 标题中出现次数降序 → 标题首次出现位置升序 → 正文首次出现位置升序。这些无法用内置 sort.Strings 或 sort.Ints 实现,必须构造带上下文的结构体并自定义比较逻辑。
- 先建临时结构体:
type HotWord struct { Word string; Total int; TitleCount int; FirstTitleIndex int; FirstContentIndex int } -
FirstTitleIndex和FirstContentIndex需在统计频次时顺手记录(第一次遇到该词时记下 article 索引或全局词序) - 排序时用
sort.Slice(hotWords, func(i, j int) bool { ... }),嵌套判断四层条件,用&&连接,不要用if-else if嵌套,易漏边界
示例关键判断段:
sort.Slice(hotWords, func(i, j int) bool {
if hotWords[i].Total != hotWords[j].Total {
return hotWords[i].Total > hotWords[j].Total
}
if hotWords[i].TitleCount != hotWords[j].TitleCount {
return hotWords[i].TitleCount > hotWords[j].TitleCount
}
if hotWords[i].FirstTitleIndex != hotWords[j].FirstTitleIndex {
return hotWords[i].FirstTitleIndex < hotWords[j].FirstTitleIndex
}
return hotWords[i].FirstContentIndex < hotWords[j].FirstContentIndex
})
topN 截断要在排序后做,别在 map 阶段提前删
常见错误是边统计边维护一个长度为 N 的最小堆,或用 heap 包动态替换。这对热词排行是过度设计——M 篇文章总词数有限(通常几千到几万),全量统计 + 全量排序再 hotWords = hotWords[:min(len(hotWords), topN)] 更直观、更不易出错。
- 提前截断会丢失排序所需的完整数据,比如两个词频相同但标题出现顺序不同,必须看到全部才能比出先后
- 内存压力可控:假设 10 万词,每个
HotWord结构体约 64 字节,总内存不到 6 MB - 若真遇到百万级词表(如全站日志聚合),再考虑分桶+采样,但那已超出“热词排行”本意,属于离线分析范畴
真正容易被忽略的是:清洗和排序逻辑必须可复现——同一输入必须产生完全相同的输出顺序,否则缓存、对比、AB 测试都会出问题。尤其 FirstTitleIndex 这类隐式序号,一旦文章加载顺序变化,就得同步重算,不能靠随机迭代 map key 来“碰运气”。


















