
Go 标准库的 sort.Search 就是高性能二分查找的最终答案,不用自己写、不该自己写;但直接传 a[i] == target 会崩,漏验证会越界,闭包捕获大对象会让性能归零。
为什么不能用 a[i] == target 当断言函数
这是最常踩的坑。你写 sort.Search(len(a), func(i int) bool { return a[i] == target }),表面上能“找到”,但语义错位:sort.Search 的设计目标是找「首个满足条件的位置」,不是“找相等”。当 target 不存在时,它仍会一路推进到 i == len(a),然后在断言里访问 a[i] panic;即使没 panic,返回值也可能是插入点(比如数组 []int{1,3,5} 查 4,返回 2,但 a[2] == 5 != 4)。
正确断言必须是升序前提下的边界判定: – 找左边界(首次出现)→ a[i] >= target – 找右边界(末次出现)→ a[i] > target,再减 1 – 降序则反向:用 a[i] 或 <code>a[i]
查完必须显式验证 a[idx] == target
sort.Search 返回的是位置,不是“是否找到”的布尔结果。它对三种边界情况都返回合法索引: – 空切片 → 返回 0 – 全小于 target → 返回 len(a) – 全大于 target → 返回 0 不检查就用 a[idx],必然 panic 或逻辑错误。
标准写法只有这一种安全模式:
idx := sort.Search(len(a), func(i int) bool { return a[i] >= target })if idx < len(a) && a[idx] == target { // 找到了,idx 是首次出现位置} else { // 没找到}
结构体切片按字段查时,别用闭包捕获整个切片
比如你要查 users []User 中 Age >= 25 的第一个用户,别这么写:
立即学习“go语言免费学习笔记(深入)”;
users := getUsers()idx := sort.Search(len(users), func(i int) bool { return users[i].Age >= 25 // ❌ 闭包捕获了整个 users 变量})
这会让编译器无法内联断言函数,且每次调用都隐式引用大对象。正确做法是直接访问字段,不引入额外变量:
idx := sort.Search(len(users), func(i int) bool { return users[i].Age >= 25 // ✅ users 是外层作用域变量,但未被闭包“捕获”为引用})
更进一步,如果 users 是 []interface{},类型断言开销明显,这时应先转成具体切片再查,别硬套 sort.Search。
sort.SearchInts 和手写 sort.Search 没性能差别
sort.SearchInts 就是 sort.Search 的一层薄封装,底层完全一样。所谓“SearchInts 更快”是误解。真正影响性能的是断言函数本身: – 避免在断言里调 map、做字符串拼接、调方法、分配内存 – users[i].CreatedAt.After(t) 比 parseTime(users[i].TimeStr).After(t) 快得多 – Go 1.21+ 能内联简单断言,但一旦涉及接口类型或间接调用,内联失败,开销立现
所以别迷信函数名,盯住你的断言函数干了什么——它会被调用 O(log n) 次,每次代价都放大。
最易被忽略的一点:mid 计算必须用 l + (r-l)/2,不是 (l + r) / 2。这不是理论问题,在处理超大偏移(如 mmap 场景下 TB 级文件索引)时,l + r 整数溢出会导致 mid 错乱,搜索彻底失效。

评论(0)