最稳方式是使用 github.com/agnivade/levenshtein 库,它支持 Unicode、内存复用、边界测试完备;自实现易在空串、中文、超长串出错,且需注意 len() 返回字节长而非字符数。

Go 标准库不提供字符串相似度计算,必须自己实现或引入第三方算法;最常用且平衡精度与性能的是 Levenshtein 距离,它直接对应编辑距离,可轻松转换为相似度百分比。
用 Levenshtein 距离实现基础相似度函数
Levenshtein 距离表示将一个字符串变为另一个所需的最少单字符编辑(插入、删除、替换)次数。相似度可定义为 1 - distance / max(len(a), len(b)),避免除零且结果在 [0,1] 区间。
注意:该公式对空字符串和极短串敏感,需单独处理边界。
- 空字符串参与比较时,
distance等于非空串长度,此时相似度为 0 —— 这符合直觉,但若业务允许“空匹配”,需额外判断 - 使用二维切片实现时,空间复杂度为 O(m×n),对超长字符串(如 >5KB)可能触发内存压力;生产环境建议改用滚动数组优化至 O(min(m,n))
- Go 中字符串底层是 UTF-8 字节序列,
len(s)返回字节数而非 rune 数;若需按 Unicode 字符计算(如含中文、emoji),必须先用utf8.RuneCountInString并遍历rune切片
func LevenshteinSimilarity(a, b string) float64 {
if a == b {
return 1.0
}
if a == "" || b == "" {
return 0.0
}
<pre class="brush:php;toolbar:false;">m, n := len(a), len(b)
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
for i := 0; i maxLen {
maxLen = n
}
if maxLen == 0 {
return 1.0
}
return 1.0 - float64(distance)/float64(maxLen)}
为什么不用 strings.Contains 或 regexp?
这类函数只解决子串匹配或模式识别,无法量化“相似程度”。比如 "apple" 和 "appel" 编辑距离为 1,相似度约 0.8,但 strings.Contains("apple", "appel") 是 false,regexp.MatchString 也无法直接给出数值结果。
- 模糊搜索场景(如用户拼写纠错、日志关键词归并)必须依赖编辑距离或其变种(如 Damerau-Levenshtein 支持相邻字符交换)
- 若只需判断“是否近似相等”,可设阈值:例如
LevenshteinSimilarity(a,b) > 0.9视为匹配 - 正则表达式适合结构化文本提取,不是相似度计算工具;滥用会导致逻辑错位和性能浪费
生产环境要注意的三个实际问题
真实服务中,相似度函数常被高频调用,容易成为瓶颈或内存热点。
- 不要在循环内反复创建大二维切片 —— 可复用
sync.Pool缓存[][]int,尤其当字符串长度相对固定时 - 对中文等多字节语言,直接用
len()会误算(如len("你好") == 6),必须转 rune:runesA := []rune(a),然后基于len(runesA)构建 DP 表 - 如果只是做去重或聚类,考虑用更轻量的
Jaro-Winkler(已有人封装为github.com/agnivade/levenshtein的扩展,或直接用github.com/yudppp/strsim)—— 它对前缀相同的情况更敏感,且计算更快
Levenshtein 是起点,不是终点;选哪种算法取决于你比较的是 URL、用户昵称还是医学术语——前缀重要就用 Jaro-Winkler,容错要支持换位就上 Damerau,而纯 ASCII 短文本用原生 Levenshtein 最稳妥。别跳过字符编码和内存复用这两步,它们在压测时最容易暴露。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











