GoFrame gmap详解 hashmap、listmap、treemap使用技巧


highlight: a11y-dark

theme: smartblue

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

文章比较硬核,爆肝2千多字,除了hashmap、listmap、treemap使用技巧阅读还有使用gmap的踩坑之旅,阅读大约需要5~10分钟。

先说结论

map类型

一图胜千言:

image.png

实例化示例:

  1. hashMap := gmap.New(true)
  2. listMap := gmap.NewListMap(true)
  3. treeMap := gmap.NewTreeMap(gutil.ComparatorInt, true)

使用技巧

当我们对返回顺序有要求时不能使用hashmap,因为hashmap返回的是无序列表;

当需要按输入顺序返回结果时使用listmap;

当需要让返回结果自然升序排列时使用treemap

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/container/gmap"
  5. "github.com/gogf/gf/frame/g"
  6. "github.com/gogf/gf/util/gutil"
  7. )
  8. func main() {
  9. array := g.Slice{5, 1, 2, 7, 3, 9, 0}
  10. hashMap := gmap.New(true)
  11. listMap := gmap.NewListMap(true)
  12. treeMap := gmap.NewTreeMap(gutil.ComparatorInt, true)
  13. // 赋值
  14. for _, v := range array {
  15. hashMap.Set(v, v)
  16. listMap.Set(v, v)
  17. treeMap.Set(v, v)
  18. }
  19. //打印结果
  20. fmt.Println("hashMap.Keys() :", hashMap.Keys())
  21. fmt.Println("hashMap.Values():", hashMap.Values())
  22. //从打印结果可知hashmap的键列表和值列表返回值的顺序没有规律,随机返回
  23. fmt.Println("listMap.Keys() :", listMap.Keys())
  24. fmt.Println("listMap.Values():", listMap.Values())
  25. //listmap键列表和值列表有序返回,且顺序和写入顺序一致
  26. fmt.Println("treeMap.Keys() :", treeMap.Keys())
  27. fmt.Println("treeMap.Values():", treeMap.Values())
  28. //treemap键列表和值列表也有序返回,但是不和写入顺序一致,按自然数升序返回
  29. }

打印结果

  1. hashMap.Keys() : [5 1 2 7 3 9 0]
  2. hashMap.Values(): [2 7 3 9 0 5 1]
  3. listMap.Keys() : [5 1 2 7 3 9 0]
  4. listMap.Values(): [5 1 2 7 3 9 0]
  5. treeMap.Keys() : [0 1 2 3 5 7 9]
  6. treeMap.Values(): [0 1 2 3 5 7 9]

基础概念

GoFrame框架(下文简称gf)提供的数据类型,比如:字典gmap、数组garray、集合gset、队列gqueue、树形结构gtree、链表glist都是支持设置并发安全开关的。

支持设置并发安全开关这也是gf提供的常用数据类型和原生数据类型非常重要的区别

今天和大家分享gf框架中gmap相关知识点

对比sync.Map

go语言提供的原生map不是并发安全的map类型

go语言从1.9版本开始引入了并发安全的sync.Map,但gmap比较于标准库的sync.Map性能更加优异,并且功能更加丰富。

基础使用

  1. gmap.New(true) 在初始化的时候开启并发安全开关
  2. 通过 Set() 方法赋值,通过 Sets() 方法批量赋值
  3. 通过 Size() 方法获取map大小
  4. 通过 Get() 根据key获取value值

为了方便大家更好的查看效果,在下方代码段中标明了打印结果

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/container/gmap"
  5. )
  6. func main() {
  7. m := gmap.New(true)
  8. // 设置键值对
  9. for i := 0; i < 10; i++ {
  10. m.Set(i, i)
  11. }
  12. fmt.Println("查询map大小:", m.Size())
  13. //批量设置键值对
  14. m.Sets(map[interface{}]interface{}{
  15. 10: 10,
  16. 11: 11,
  17. })
  18. // 目前map的值
  19. fmt.Println("目前map的值:", m)
  20. fmt.Println("查询是否存在键值对:", m.Contains(1))
  21. fmt.Println("根据key获得value:", m.Get(1))
  22. fmt.Println("删除数据", m.Remove(1))
  23. //删除多组数据
  24. fmt.Println("删除前的map大小:", m.Size())
  25. m.Removes([]interface{}{2, 3})
  26. fmt.Println("删除后的map大小:", m.Size())
  27. //当前键名列表
  28. fmt.Println("键名列表:", m.Keys()) //我们发现是无序列表
  29. fmt.Println("键值列表:", m.Values()) //我们发现也是无序列表
  30. //查询键名,当键值不存在时写入默认值
  31. fmt.Println(m.GetOrSet(20, 20)) //返回值是20
  32. fmt.Println(m.GetOrSet(20, "二十")) //返回值仍然是20,因为key对应的值存在
  33. m.Remove(20)
  34. fmt.Println(m.GetOrSet(20, "二十")) //返回值是二十,因为key对应的值不存在
  35. // 遍历map
  36. m.Iterator(func(k interface{}, v interface{}) bool {
  37. fmt.Printf("%v:%v \n", k, v)
  38. return true
  39. })
  40. //自定义写锁操作
  41. m.LockFunc(func(m map[interface{}]interface{}) {
  42. m[88] = 88
  43. })
  44. // 自定义读锁操作
  45. m.RLockFunc(func(m map[interface{}]interface{}) {
  46. fmt.Println("m[88]:", m[88])
  47. })
  48. // 清空map
  49. m.Clear()
  50. //判断map是否为空
  51. fmt.Println("m.IsEmpty():", m.IsEmpty())
  52. }

运行结果

image.png

合并 merge

注意:Merge()的参数需要是map的引用类型,也就是传map的取址符。

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/container/gmap"
  5. )
  6. func main() {
  7. var m1, m2 gmap.Map
  8. m1.Set("k1", "v1")
  9. m2.Set("k2", "v2")
  10. m1.Merge(&m2)
  11. fmt.Println("m1.Map()", m1.Map()) //m1.Map() map[k1:v1 k2:v2]
  12. fmt.Println("m2.Map()", m2.Map()) //m2.Map() map[k2:v2]
  13. }

序列化

正如上一篇 GoFrame glist 基础使用和自定义遍历 介绍的,gf框架提供的数据类型不仅支持设置并发安全,也都支持序列化和反序列化。

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "github.com/gogf/gf/container/gmap"
  6. )
  7. func main() {
  8. // 序列化
  9. //var m gmap.Map
  10. m := gmap.New() //必须实例化 只是像上面声明但是不进行实例化,是无法序列化成功的
  11. m.Sets(map[interface{}]interface{}{
  12. "name": "王中阳",
  13. "age": 28,
  14. })
  15. res, _ := json.Marshal(m)
  16. fmt.Println("序列化结果:", res) //打印结果:{"age":28,"name":"王中阳"}
  17. // 反序列化
  18. m2 := gmap.New()
  19. s := []byte(`{"age":28,"name":"王中阳"}`)
  20. _ = json.Unmarshal(s, &m2)
  21. fmt.Println("反序列化结果:", m2.Map()) //反序列化结果: map[age:28 name:王中阳]
  22. }

踩坑

正如上面代码段中注释提到的:

在进行序列化操作时,必须实例化map

  1. m := gmap.New()

image.png

只是声明map而不进行实例化,是无法序列化成功的

  1. var m gmap.Map

image.png

过滤空值

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/container/gmap"
  5. )
  6. func main() {
  7. //首先明确:空值和nil是不一样的,nil是未定义;而空值包括空字符串,false、0等
  8. m1 := gmap.NewFrom(map[interface{}]interface{}{
  9. "k1": "",
  10. "k2": nil,
  11. "k3": 0,
  12. "k4": false,
  13. "k5": 1,
  14. })
  15. m2 := gmap.NewFrom(map[interface{}]interface{}{
  16. "k1": "",
  17. "k2": nil,
  18. "k3": 0,
  19. "k4": false,
  20. "k5": 1,
  21. })
  22. m1.FilterEmpty()
  23. m2.FilterNil()
  24. fmt.Println("m1.FilterEmpty():", m1) //预测结果: k5:1
  25. fmt.Println("m2.FilterNil():", m2) //预测结果:除了k2,其他都返回
  26. // 打印结果和预期的一致:
  27. //m1.FilterEmpty(): {"k5":1}
  28. //m2.FilterNil(): {"k1":"","k3":0,"k4":false,"k5":1}
  29. }

打印结果

  1. m1.FilterEmpty(): {"k5":1}
  2. m2.FilterNil(): {"k1":"","k3":0,"k4":false,"k5":1}

键值对反转 Flip

  1. package main
  2. import (
  3. "github.com/gogf/gf/container/gmap"
  4. "github.com/gogf/gf/frame/g"
  5. )
  6. func main() {
  7. // 键值对反转flip
  8. var m gmap.Map
  9. m.Sets(map[interface{}]interface{}{
  10. "k1": "v1",
  11. "k2": "v2",
  12. })
  13. fmt.Println("反转前:", m.Map())
  14. m.Flip()
  15. fmt.Println("反转后:", m.Map())
  16. }

打印结果

  1. 反转前:{
  2. "k1": "v1",
  3. "k2": "v2"
  4. }
  5. 反转后:{
  6. "v1": "k1",
  7. "v2": "k2"
  8. }

出栈(随机出栈)

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gogf/gf/container/gmap"
  5. )
  6. func main() {
  7. //pop pops map出栈(弹栈)
  8. var m gmap.Map
  9. m.Sets(map[interface{}]interface{}{
  10. 1: 1,
  11. 2: 2,
  12. 3: 3,
  13. 4: 4,
  14. 5: 5,
  15. })
  16. fmt.Println("m.Pop()之前:", m.Map())
  17. key, value := m.Pop()
  18. fmt.Println("key:", key)
  19. fmt.Println("value:", value)
  20. fmt.Println("m.Pop()之后:", m.Map()) //多次测试后发现是随机出栈,不能理所当然的认为按顺序出栈
  21. res := m.Pops(2) //参数是出栈个数
  22. fmt.Println("res:", res)
  23. fmt.Println("m.Pops之后:", m.Map()) //多次测试之后发现也是随机出栈
  24. }

运行结果

image.png

踩坑

注意:多次测试后发现是随机出栈,不能理所当然的认为按顺序出栈

总结

通过这篇文章,我们了解到:

  1. 重点消化一下map遍历时,不同map的特点:

    1.1 当我们对返回顺序有要求时不能使用hashmap,因为hashmap返回的是无序列表;

    1.2 当需要按输入顺序返回结果时使用listmap;

    1.3 当需要让返回结果自然升序排列时使用treemap

  2. gmap的基础使用和进阶使用技巧:反转map、序列化、合并map、出栈等。

  3. gf框架提供的数据结构,比如:字典gmap、数组garray、集合gset、队列gqueue、树形结构gtree、链表glist 都是支持设置并发安全开关的;而且都支持序列化和反序列化,实现了标准库json数据格式的序列化/反序列化接口。

最后

感谢阅读,欢迎大家三连:点赞、收藏、投币(关注)!!!

8e95dac1fd0b2b1ff51c08757667c47a.gif


文章标签:

原文连接:https://juejin.cn/post/7101797623484383246

相关推荐

Flask框架——消息闪现

34个图片压缩工具集合,包含在线压缩和CLI工具

入门即享受!coolbpf 硬核提升 BPF 开发效率 | 龙蜥技术

基于 OPLG 从 0 到 1 构建统一可观测平台实践

全链路灰度在数据库上我们是怎么做的?

冴羽答读者问:过程比结果重要吗?如果是,怎么理解?如果不是,又怎么解?

接口文档管理工具,选yapi 还是 Apifox? 这里列出了两款软件的深度分析,看完再下载不迟。

基于 Docker 来部署 Vue 或 React 前端项目及 Node 后端服务

三十岁的我,自由了!

如何实现带timeout的input?

统计千行代码Bug率,有没有意义?

814. 二叉树剪枝 : 简单递归运用题

【综合笔试题】难度 3.5\u002F5,多解法热门二叉树笔试题

为什么设计的软件不好用?那是因为不熟悉软件开发模型!一文熟悉软件开发模型

作为前端,我是这样从零实现CI\u002FCD二(node服务部署及前后端联调)

极智开发 | 讲解 Nginx 特性之一:反向代理

Netty 案例之 IM 方案设计

从 Google 离职,前Go 语言负责人跳槽小公司

最终一致性性分布式事务 TCC

不谈源码,聊聊位运算的实际应用