您现在的位置是:亿华云 > 知识
Go map 加有序排序的一些挣扎
亿华云2025-10-04 03:32:36【知识】7人已围观
简介来源:脑子进煎鱼了最近我有一个朋友又跟 map 扯上关系了,翻了个车。写 Go 项目真的是和 map 藕断丝连,时刻要注意。今天看到社区内 map 加有序排序又各种挣扎过好几轮。今天抛砖引玉。看看大家
来源:脑子进煎鱼了
最近我有一个朋友又跟 map 扯上关系了,有序翻了个车。排序写 Go 项目真的有序是和 map 藕断丝连,时刻要注意。排序
今天看到社区内 map 加有序排序又各种挣扎过好几轮。有序今天抛砖引玉。排序看看大家有没有好的有序思路。
快速背景
Go 提供了一种内置的排序 map 类型,它实现了一个哈希表,有序在 Go 程序中普遍应用广泛,排序能够做一系列的有序增删改查。
类型签名如下:
map[KeyType]ValueType
最小演示代码:
func main(){
m := make(map[int32]string)
m[0] = "煎鱼1" m[1] = "煎鱼2" m[2] = "煎鱼3" m[3] = "煎鱼4" m[4] = "煎鱼5" m[5] = "煎鱼6" for k,排序 v := rangem {
log.Printf("k: %v, v: %v", k, v)
}
}
输出结果:
$ go run demo.go
2023/12/28 23:36:02 k: 2, v: 煎鱼3
2023/12/28 23:36:02 k: 3, v: 煎鱼4
2023/12/28 23:36:02 k: 4, v: 煎鱼5
2023/12/28 23:36:02 k: 5, v: 煎鱼6
2023/12/28 23:36:02 k: 0, v: 煎鱼1
2023/12/28 23:36:02 k: 1, v: 煎鱼2
输出结果看着没有什么问题。但细心查看的有序同学应该发现了。在遍历 map 类型时,排序访问结果是有序无序的。明显多执行几次就会发现与我们声明的顺序不一致。
社区讨论
这可不。这么尴尬的无序结果。对于初学者而言很容易导致潜在的 BUG,引发一些问题/事故。
大家为此做出过努力。提出过各种提案。例如:
《proposal: Go 2: add native type for map that maintains key order[1]》《proposal: add string key sorted map as built in type[2]》《proposal: Add a sorting function for map[3]》基于这些提案,服务器租用也有人提出了新的关键字 sortedMap:
typeUser struct{
UserId string
Name string
}
func main(){
db := sortedMap[string]*User{ }
db["煎鱼2"] = &User{ UserId:"煎鱼2",Name:"n2"}
db["煎鱼1"] = &User{ UserId:"煎鱼1",Name:"n1"}
foruserId, user := range db {
fmt.Println(userId, user.Name)
}
// should output
// 煎鱼1 n1
// 煎鱼2 n2
}
但最终都失败告终。原因不外乎以下几类原因:
Go1 兼容性保障规范:对 map 类型从无序改到有序,必然会存在破坏性变更。这活现在干不了。以后未来的 Go2 再说吧(拖字诀)。排序后 map 的速度慢:如果将默认映射类型改为排序映射,那么不需要排序映射实现或已编写代码在需要时对键进行排序的人都会受到惩罚。(via @Dave Cheney)实现稳定排序的 map 麻烦:Go 在标准库 fmt 里实现了稳定排序的 map 输出,整体实现较为难以解决。言外之意不建议将稳定排序加入 map 类型的支持内。(via @Rob Pike)参考实现
Go 核心团队推荐查看:https://github.com/golang/go/blob/master/src/internal/fmtsort/sort.go 的实现逻辑。如果要对 map 类型做稳定排序。要做一样的事情,甚至更多。
说白了,就是要做大小对比、顺序排序。还要适配所有的类型。
对应源代码的其中一角:
type SortedMap struct{
Key []reflect.Value
Value []reflect.Value
}
func (o *SortedMap) Len() int { return len(o.Key) }
func (o *SortedMap) Less(i, j int) bool { return compare(o.Key[i], o.Key[j]) < 0}
func (o *SortedMap) Swap(i, j int){
o.Key[i], o.Key[j] = o.Key[j], o.Key[i]
o.Value[i], o.Value[j] = o.Value[j], o.Value[i]
}
func Sort(mapValue reflect.Value) *SortedMap{
ifmapValue.Type().Kind() != reflect.Map {
return nil}
n := mapValue.Len()
key := make([]reflect.Value, 0, n)
value := make([]reflect.Value, 0, n)
iter := mapValue.MapRange()
foriter.Next() {
key = append(key, iter.Key())
value = append(value, iter.Value())
}
sorted := &SortedMap{
Key: key,
Value: value,
}
sort.Stable(sorted)
returnsorted
}
func compare(aVal, bVal reflect.Value) int{
aType, bType := aVal.Type(), bVal.Type()
ifaType != bType {
return -1 // No good answer possible, but dont return 0: theyre not equal.}
switchaVal.Kind() {
casereflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
a, b := aVal.Int(), bVal.Int()
switch{
casea < b:
return -1 casea > b:
return 1 default:
return 0}
casereflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
...
}
如果只是标准库 fmt 输出流用到,常规的服务器托管实现就行。但如果内置到 map 类型中,就还要考量性能、开销、可维护性的综合因素。折腾起来也是不太妙的,会引入不少的复杂度。
总结
虽然社区很多人提过很多种不同的建议。但,显然,Go 核心团队不是无法实现一个带稳定排序的 map 类型。
为此在实现上要付出较高的改造代价和性能、开销风险。外加 less is more 的设计哲学。导致此项功能特性一直停滞不前。
这个大背景下,大家也慢慢还是选择了第三条路。使用第三方库,或者配合 slice 来做,要不就是基于近年的泛型来实现了
参考资料
[1]proposal: Go 2: add native type for map that maintains key order: https://github.com/golang/go/issues/41289
[2]proposal: add string key sorted map as built in type: https://github.com/golang/go/issues/22865
[3]proposal: Add a sorting function for map: https://github.com/golang/go/issues/39291
很赞哦!(9314)
相关文章
- 用户邮箱的静态密码可能已被钓鱼和同一密码泄露。在没有收到安全警报的情况下,用户在适当的时间内不能更改密码。在此期间,攻击者可以随意输入帐户。启用辅助身份验证后,如果攻击者无法获取移动电话动态密码,他将无法进行身份验证。这样,除非用户的电子邮件密码和手机同时被盗,否则攻击者很难破解用户的邮箱。
- 不会编程也能写程序 - Testin AI 新产品iTestin发布
- 什么是Docker?与虚拟机有什么区别?
- 闲鱼上哪些商品抢手?Python分析后告诉你
- 旧域名的外链是否会对新建站点产生影响?
- 哈工大硕士生用 Python 实现了 11 种经典数据降维算法,源代码库已开放
- Python算法中的时间复杂度
- 90行JS代码构建属于你的React
- 用户邮箱的静态密码可能已被钓鱼和同一密码泄露。在没有收到安全警报的情况下,用户在适当的时间内不能更改密码。在此期间,攻击者可以随意输入帐户。启用辅助身份验证后,如果攻击者无法获取移动电话动态密码,他将无法进行身份验证。这样,除非用户的电子邮件密码和手机同时被盗,否则攻击者很难破解用户的邮箱。
- 如何利用缓存机制实现Java类反射性能提升30倍
站长推荐
在数以亿计的网站中,我们应该抓住每一个可能带来宣传的机会,域名可以带有企业的名字,一般可以使用汉语拼音或者英语单词或者是相关缩写的形式,只要用户记住了你企业的名字,就能很容易的打出你的网站域名,同样的,记住了网站域名也能很快的记住你公司的名字。
你太菜了,竟然不知道Code Review?
不给糖果就捣乱,用Python绘制有趣的万圣节南瓜怪
【NCTS峰会回顾】Testin徐琨:AI引领下一代测试,iTestin改写测试未来
新手可以注册cc域名吗?cc域名有什么特点?
MySQL的COUNT语句,竟然都能被面试官虐的这么惨!?
2019年度Java开发者路线图
Java性能优化:35个小细节,提升你的Java代码运行效率