因为strings.Contains仅返回布尔值,无法满足匹配位置、重叠匹配、自定义比较等需求;手写匹配可控制索引推进、边界处理、UTF-8 rune对齐及重叠逻辑,且小文本下暴力法比KMP更高效。

为什么不用 strings.Contains 而要手写匹配?
因为真实场景里,strings.Contains 只能回答“是否包含”,但你常需要:匹配位置、重叠匹配、自定义比较(比如忽略大小写但保留原始偏移)、或嵌入到更复杂的协议解析中。一旦需求超出“有没有”,就得碰底层逻辑。
手写也并非为了造轮子——而是理解 index 如何推进、len(s) 和 len(pattern) 边界怎么卡、以及为什么暴力法在小文本上反而比 KMP 更快。
暴力匹配的边界条件怎么写才不越界?
最常出错的是循环上限:用 i ,而不是 <code>i 。少等号就漏掉末尾刚好对齐的一次检查;多一步就 panic: index out of range。
还要提前判空:if len(pattern) == 0 应该返回 0(Go 社区惯例,空串在任意位置都算匹配);if len(pattern) > len(s) 直接返回 -1。
立即学习“go语言免费学习笔记(深入)”;
- 别用
s[i:i+len(pattern)] == pattern做切片比较——它会复制子串,小字符串还行,大文本+高频调用时 GC 压力明显 - 改用逐字符比对,一发现不等立刻 break,平均性能更好
- 注意 UTF-8 字符:如果 pattern 含中文或 emoji,
len()返回的是字节数,不是 rune 数;此时必须用utf8.RuneCountInString并转 rune 切片,否则位置计算全错
如何让匹配支持重叠结果?
默认暴力匹配找到一个就 return i,但比如在 "aaaa" 中找 "aa",重叠匹配应返回 [0,1,2],而非仅 0。关键在匹配成功后,只将 i 自增 1,而不是跳过整个 pattern 长度。
示例片段:
for i := 0; i <= len(s)-len(pattern); i++ {
match := true
for j := 0; j < len(pattern); j++ {
if s[i+j] != pattern[j] {
match = false
break
}
}
if match {
results = append(results, i)
// 这里不 break,也不 i += len(pattern),只 i++ 继续下一位
}
}重叠模式下,时间复杂度退化为 O(n×m),但语义正确性优先于理论最优——尤其当 pattern 极短(如 2~3 字节)时,实际性能损失可忽略。
Case-insensitive 匹配为什么不能直接用 strings.ToLower?
因为 strings.ToLower 会分配新字符串,且对非 ASCII 字符(如德语 ß、土耳其语 İ)行为依赖 locale,而 Go 的 strings.EqualFold 是专为此设计的常量时间、无内存分配、符合 Unicode 标准的比较函数。
正确做法是不用预转换,而在内层循环用:
if !strings.EqualFold(string(s[i+j]), string(pattern[j])) { ... }但注意:string(s[i+j]) 会创建单字符字符串,有小开销;若确定输入全是 ASCII,可手动做 lower(s[i+j]) == lower(pattern[j]),用位运算实现,更快更轻量。
真正容易被忽略的是:EqualFold 对长度不同的 rune 序列也可能返回 true(比如 “ffi” 和 “ffi”),如果你的业务要求严格字面等价,那就不能用它——得先统一转 rune 切片再逐 rune 比较。


















