contains 更快:index 多索引追踪和边界处理,实测慢5%–10%;但真正影响性能的是数据结构选择——高频查存在性应预建 map,小切片(

Go 里 Contains 和 Index 哪个更快?
没有统一答案:取决于你查的是什么类型、数据量多大、是否需要位置信息。标准库的 slices.Contains 和 slices.Index(Go 1.21+)底层都是线性扫描,但 Index 多一次赋值和分支判断,实测慢 5%–10%,差别微乎其微;真正影响性能的是你用的底层数据结构——切片查得再快也干不过 map 查 O(1)。
什么时候该用 map 而不是反复调 slices.Contains?
只要你在循环里、或高频路径中对同一组元素做多次“是否存在”判断,就该预建 map。比如校验用户权限列表、过滤黑名单 ID、查白名单域名。
- 常见错误现象:
for _, id := range reqIDs { if slices.Contains(allowed, id) { ... } },allowed是固定 100 个字符串,但每次循环都扫一遍 - 正确做法:提前构建
allowedSet := make(map[string]struct{}, len(allowed)); for _, s := range allowed { allowedSet[s] = struct{}{} },后续用if _, ok := allowedSet[id]; ok { ... } - 性能影响:100 元素切片查 1 万次,约 100×10000 = 100 万次比较;等价 map 查只需 1 万次哈希+一次指针跳转,实测快 80 倍以上
- 注意兼容性:Go slices 包,得自己写或用
golang.org/x/exp/slices(已弃用),不如直接上 map 稳定
slices.Index 比 slices.Contains 多做了什么?
它不只是多返回一个 int:内部逻辑多了索引追踪和 early-return 控制流,且必须处理 -1 边界情况。如果你只需要“有没有”,别用 Index 白费那点 CPU。
- 使用场景:只有当你确实需要知道元素在切片中的下标时才用
slices.Index,比如要删掉它、或取它后面的元素 - 参数差异:
slices.Contains[T comparable](s []T, v T) bool只要类型可比较;slices.Index[T comparable](s []T, v T) int同样要求T可比较,但返回值语义更重 - 容易踩的坑:用
slices.Index(s, x) != -1替代slices.Contains(s, x)—— 语义等价但多一次整数比较和分支预测失败开销,压测下可测出差异
小切片(
不值得。编译器常把极短切片的线性查找内联并展开成几个 cmp+jmp,比哈希表初始化+内存分配还轻。这时候代码清晰度优先。
立即学习“go语言免费学习笔记(深入)”;
- 典型场景:HTTP handler 里检查 method 是否在
[]string{"GET", "POST", "PUT"}中 - 实操建议:别急着建 map,直接写
if method == "GET" || method == "POST" || ...或用slices.Contains,可读性好、无额外分配、性能无损 - 为什么:map 创建涉及内存分配、哈希计算、桶初始化,对于 3–5 元素,这些开销远超一次 3 次比较
- 容易被忽略的点:很多人一看到“查集合”就条件反射上 map,却没想清楚数据规模和调用频次——这是最常发生的过早优化











