Go slices 怎样用 BinarySearch 查找有序切片
如果一段 Go 数据已经按升序排好,想知道某个值是否存在,或者想知道新值应该插在哪里,直接使用 slices.BinarySearch。它返回两个结果:第一个是索引,第二个是 found。命中时索引指向目标值;未命中时索引仍然有用,它就是目标值保持有序时应插入的位置。
slices.BinarySearch要求切片按升序排列,不能把无序数据直接传进去。found=false时,返回的索引不是错误值,而是有序插入点,可能等于切片长度。- 结构体或自定义排序使用
slices.BinarySearchFunc,比较函数必须和切片的排序规则一致。

BinarySearch 先解决的是什么问题
slices 是 Go 标准库中的切片工具包。BinarySearch 的签名可以概括为:给定一个升序切片和一个目标值,返回目标值最早出现的位置,以及是否真的找到。整数、浮点数和字符串等满足有序比较约束的元素可以直接使用它。
这里的关键不是“它会自动排序”,而是“它在已经排序的数据上查找”。如果数据来自追加写入,调用前应先完成排序;查找过程中不要把切片内容改成破坏升序的状态。
package main
import (
"fmt"
"slices"
)
func main() {
// 切片必须按升序排列,BinarySearch 才能返回可靠结果。
prices := []int{10, 20, 35, 50, 80}
// 命中时 index 指向目标值,found 表示确实存在。
index, found := slices.BinarySearch(prices, 35)
fmt.Println(index, found) // 2 true
// 未命中时 index 是保持升序所需的插入位置。
index, found = slices.BinarySearch(prices, 40)
fmt.Println(index, found) // 3 false
}
这个例子中,40 应放在 35 和 50 之间,所以返回索引 3。它不代表“离目标最近的元素”,也不是最后一次比较的位置,而是可以直接用于插入的边界。
怎样读懂 index 和 found
实际代码中应始终同时处理两个返回值。命中时使用 found 保护索引访问;未命中时可以把 index 交给插入逻辑、范围判断或去重逻辑。
| 场景 | 返回值含义 | 常见用途 |
|---|---|---|
| 目标已存在 | index 指向最早匹配项,found=true | 读取、更新或拒绝重复值 |
| 目标位于两个元素之间 | index 是中间插入点,found=false | 保持升序插入 |
| 目标小于首元素 | index=0,found=false | 插入到切片开头 |
| 目标大于末元素 | index=len(x),found=false | 追加到切片末尾 |
空切片也遵循同一规则:返回索引为 0、found=false。因此不要把 index=0 单独当成命中标志。
重复值为什么返回最早位置
有重复值时,BinarySearch 返回目标值第一次出现的位置。这一点适合做“是否存在”判断,也适合把一组相同值的范围从左边界开始定位。如果业务只需要任意一个匹配项,就不要依赖重复值中间某个位置的偶然结果。
package main
import (
"fmt"
"slices"
)
func main() {
// 重复值仍需保持整体升序,返回值会指向最早的 7。
levels := []int{3, 7, 7, 7, 12}
index, found := slices.BinarySearch(levels, 7)
// found 用来确认命中,index 用来读取第一处匹配。
if found {
fmt.Println("first match:", index, levels[index])
}
}
结构体字段查找用 BinarySearchFunc
当切片元素是结构体,不能直接用普通 BinarySearch 比较整个结构体。这时使用 BinarySearchFunc,让比较函数只关注排序字段。比较函数返回负数、零或正数,分别表示切片元素排在目标前面、相等或排在目标后面。
package main
import (
"fmt"
"slices"
"strings"
)
type User struct {
Name string
Age int
}
func main() {
users := []User{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 24},
{Name: "Vera", Age: 41},
}
// users 按 Name 升序排列,比较函数也必须按 Name 判断先后。
index, found := slices.BinarySearchFunc(users, User{Name: "Bob"}, func(item, target User) int {
return strings.Compare(item.Name, target.Name)
})
if found {
// 命中后再读取完整结构体,比较时忽略的 Age 不会丢失。
fmt.Println(index, users[index].Name, users[index].Age)
}
}
如果切片实际按年龄排序,比较函数却按姓名比较,结果就没有意义。排序字段、比较函数和后续插入逻辑必须使用同一套顺序;降序数据也不能直接套用普通升序规则。

把返回位置接到去重和有序插入
返回位置最实用的场景是“先查后写”。命中时不重复插入;未命中时,把新值插入到该索引处。下面的示例用 slices.Insert 复用同一个边界,避免重新手写移动元素的细节。
package main
import (
"fmt"
"slices"
)
func insertUnique(values []int, value int) []int {
// 先取得命中状态和有序插入点,不命中时 index 仍可直接使用。
index, found := slices.BinarySearch(values, value)
if found {
// 已存在就保持原切片内容,避免产生重复记录。
return values
}
// 插入点来自二分查找,因此结果继续保持升序。
return slices.Insert(values, index, value)
}
func main() {
values := []int{10, 20, 50}
values = insertUnique(values, 35)
values = insertUnique(values, 20)
fmt.Println(values) // [10 20 35 50]
}
需要注意,查找本身是对数级定位,但切片中间插入仍可能移动后续元素。数据量很大、插入频率很高时,应把“查找成本”和“搬移成本”分开评估,不能只因为用了二分查找就认为整个写入过程都是 O(log n)。
几个容易踩到的边界
- 无序切片:不要把无序数据直接传给
BinarySearch,先明确排序规则并完成排序。 - 降序切片:普通函数要求升序;如果使用自定义函数,比较规则必须和切片实际顺序完全一致。
- found 判断:索引为零不代表命中,必须检查第二个返回值。
- 重复值:命中后得到的是最早匹配位置,不要据此推断所有重复值的范围已经被返回。
- 自定义比较:比较函数返回零的条件要和业务的“相等”定义一致,否则可能把不同记录当成重复。
常见问题
BinarySearch 找不到时会返回 -1 吗?
不会。未命中时返回保持升序所需的插入位置,范围是 0 到 len(x),再通过 found=false 表示目标不存在。
为什么不能把 map 或无序切片直接拿来二分查找?
二分查找依赖元素顺序来缩小范围。无序数据不满足这个前提,返回的索引就没有可靠含义;map 也没有可供该 API 使用的连续索引顺序。
官方 API 说明在哪里?
可以直接查看标准库文档:https://pkg.go.dev/slices#BinarySearch,自定义比较函数参见 https://pkg.go.dev/slices#BinarySearchFunc。如果需要确认该包的引入背景,也可阅读 https://go.dev/doc/go1.21。
总结一下:先保证切片顺序,再同时读取 index 和 found。命中时索引用于读取,未命中时索引用于插入;元素是结构体时,把排序字段准确写进 BinarySearchFunc 的比较函数。
Go sync/atomic 怎样用 atomic.Int64 记录有符号偏移量
- 上一篇
- Go sync/atomic 怎样用 atomic.Int64 记录有符号偏移量
- 下一篇
- Go slices 怎样按比较函数稳定排序结构体切片
-
- Golang · Go教程 | 11分钟前 |
- Go crypto/x509 怎样从 PEM 中读取多个证书块
- 437浏览 收藏
-
- Golang · Go教程 | 11分钟前 |
- Go crypto/tls 怎样用 VerifyConnection 补充证书判断
- 478浏览 收藏
-
- Golang · Go教程 | 14分钟前 |
- Go crypto/tls 怎样为 TLS 客户端配置 ALPN
- 367浏览 收藏
-
- Golang · Go教程 | 16分钟前 | TLS · Go教程 · Go crypto/tls ConnectionState TLS会话恢复 DidResume
- Go crypto/tls 怎样通过 ConnectionState 判断会话是否恢复
- 230浏览 收藏
-
- Golang · Go教程 | 18分钟前 |
- Go compress/zlib 怎样根据 zlib 字典解压输入
- 499浏览 收藏
-
- Golang · Go教程 | 21分钟前 | 标准库 · go · 性能 · 压缩 · Go reset compress/gzip gzip.Writer 压缩复用
- Go compress/gzip 怎样复用 gzip Writer 并正确重置
- 330浏览 收藏
-
- Golang · Go教程 | 22分钟前 |
- Go archive/zip 怎样读取 ZIP 条目的 Extra 字段
- 385浏览 收藏
-
- Golang · Go教程 | 23分钟前 |
- Go compress/gzip 怎样从 gzip Header 读取原始文件名
- 318浏览 收藏
-
- Golang · Go教程 | 27分钟前 |
- Go archive/zip 怎样从 ZIP 文件名编码判断 UTF-8 标志
- 163浏览 收藏
-
- Golang · Go教程 | 28分钟前 | 错误处理 · Go教程 · Go archive/tar io.ErrUnexpectedEOF tar Reader 流式解包
- Go archive/tar 怎样处理 tar Reader 的截断输入
- 244浏览 收藏
-
- Golang · Go教程 | 30分钟前 |
- Go archive/tar Header.AccessTime 怎样保留文件访问时间
- 118浏览 收藏
-
- Golang · Go教程 | 35分钟前 |
- Go archive/tar 怎样读取 tar 目录项的类型标志
- 433浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 513次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 572次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 598次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 549次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 366次使用
-
- Java 性能优化上线清单:从定位、改造到灰度发布
- 2026-06-11 860浏览
-
- Spring Boot 压测验证:Gatling、JMeter 与性能回归门禁
- 2026-06-11 843浏览
-
- Java NMT 非堆内存排查:Direct Buffer、线程栈与 Metaspace 分析
- 2026-06-11 826浏览
-
- Spring Boot 容器内存优化:JVM 堆、非堆与 MaxRAMPercentage
- 2026-06-11 809浏览
-
- Tomcat 连接与线程参数调优:maxThreads、acceptCount 与 KeepAlive
- 2026-06-11 792浏览
