strings.contains 底层直接调用 strings.index 并判断返回值是否为 -1,语义更聚焦存在性;性能略慢约 9% 源于额外整数比较与分支跳转,而非算法差异。

strings.Contains 和 strings.Index 的底层关系
strings.Contains 内部直接调用 strings.Index,再判断返回值是否为 -1。源码里就这一行:return Index(s, substr) != -1。这意味着它不可能比 Index 更快,只是语义更聚焦于“存在性”。你看到的性能差异(比如基准测试中 Contains 慢 8%~12%)主要来自那一次额外的整数比较和分支跳转,而非算法不同。
什么时候该用 strings.Contains,什么时候该用 strings.Index
只关心“有没有”时,无条件选 strings.Contains:语义清晰、意图明确、编译器也更容易做优化。但如果你接下来马上要取子串位置(比如切分、替换、高亮),就该一步到位用 strings.Index,避免重复扫描主串。
- 错误写法:
if strings.Contains(s, "key") { i := strings.Index(s, "key"); ... }→ 扫两遍 - 正确写法:
if i := strings.Index(s, "key"); i != -1 { ... }→ 扫一遍,还能复用i - 空子串行为一致:
strings.Contains("a", "")和strings.Index("a", "")都返回true/0,无需额外适配
真实性能差距有多大?别被数字带偏
在 Go 1.21+ 下,对 1KB 字符串做百万次查找,Contains 比 Index 慢约 9%,绝对耗时差不到 15ns/次。这种量级差异在绝大多数业务代码里根本不可见——真正卡住你的从来不是这十几个纳秒,而是反复在循环里对同一主串调用多次 Contains,或者误把 Contains 当成前缀/后缀判断工具。
- 查 5 个关键词?别连写 5 次
Contains,改用单次strings.Index循环或预建map[string]struct{} - 验证路径开头?用
strings.HasPrefix,它不扫描全文,只比前 N 字节,快近一倍 - 主串极大(如读文件)、子串极短(如 ASCII 符号)?考虑
bytes.Index,省掉字符串转字节切片的隐式开销
容易忽略的 Unicode 和大小写陷阱
strings.Contains 和 strings.Index 都工作在字节层面,对合法 UTF-8 安全,但完全不感知 rune 边界。你从中文字符串里用下标截出的“子串”,如果刚好切在某个汉字的 UTF-8 多字节中间,传给这两个函数就会匹配失败——不是函数有问题,是你传入的子串本身已损坏。
- 需要大小写无关匹配?必须显式转换:
strings.Contains(strings.ToLower(s), strings.ToLower(substr)),别指望函数自动处理 - 用户输入含
*或??这不是子串匹配场景,该上regexp包,硬套Contains会漏匹配或误匹配 - Go 1.22+ 的 SIMD 加速只对纯 ASCII 子串生效;含中文、emoji 时仍走朴素算法,别预期有数量级提升











