4

GoFrame的gmap相比Go原生的map,天然支持排序和有序遍历!?

 1 year ago
source link: https://www.51cto.com/article/721929.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

GoFrame的gmap相比Go原生的map,天然支持排序和有序遍历!?

作者:王中阳Go 2022-11-03 09:28:20
GoFrame提供的gmap字典类型,包含多个数据结构的map容器:HashMap、TreeMap和ListMap。其中TreeMap支持排序,TreeMap和ListMap支持有序遍历。

有好多初学GO的小伙伴都被Go语言中map的无序性“坑过”。尤其是PHP转Go的小伙伴~

这篇文章会为大家介绍:

GoFrame的gmap相比于Go原生的map有什么优势?为什么天然支持排序和有序遍历?如何做到的?

GoFrame的gmap有哪些使用技巧?

GoFrame提供的gmap字典类型,包含多个数据结构的map​容器:HashMap、TreeMap和ListMap​。其中TreeMap​支持排序,TreeMap和ListMap支持有序遍历。

我们在使用GoFrame的gmap时,要结合自己的场景使用合适的map容器:

  • 当我们对返回顺序有要求时不能使用HashMap​,因为HashMap返回的是无序列表;
  • 当需要按输入顺序返回结果时使用ListMap;
  • 当需要让返回结果按照自然升序排列时使用TreeMap;

注意:gmap的实例化默认是HashMap​类型:hashMap := gmap.New(true)​

一图胜千言

GoFrame gmap 基本介绍:

支持并发安全开关选项的map容器,最常用的数据结构。

该模块包含多个数据结构的map​容器:HashMap、TreeMap和ListMap。

58601ce38b8912ed27f38193d670c36fd23cc7.png

实例化示例:

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

实践得真知

package main

import (
   "fmt"
   "github.com/gogf/gf/v2/container/gmap"
   "github.com/gogf/gf/v2/frame/g"
   "github.com/gogf/gf/v2/util/gutil"
)

func main() {
   array := g.Slice{1, 5, 2, 3, 4, 6, 8, 7, 9}
   hashMap := gmap.New(true)
   listMap := gmap.NewListMap(true)
   treeMap := gmap.NewTreeMap(gutil.ComparatorInt, true)
   for _, v := range array {
      hashMap.Set(v, v)
   }
   for _, v := range array {
      listMap.Set(v, v)
   }
   for _, v := range array {
      treeMap.Set(v, v)
   }

   fmt.Println("HashMap   Keys:", hashMap.Keys())   //HashMap   Keys: [7 9 1 5 2 4 6 3 8]
   fmt.Println("HashMap Values:", hashMap.Values()) //HashMap Values: [6 7 9 1 5 2 4 3 8]
   //从打印结果可知hashmap的键列表和值列表返回值的顺序没有规律,随机返回
   fmt.Println("ListMap   Keys:", listMap.Keys())   //ListMap   Keys: [1 5 2 3 4 6 8 7 9]
   fmt.Println("ListMap Values:", listMap.Values()) //ListMap Values: [1 5 2 3 4 6 8 7 9]
   //listmap键列表和值列表有序返回,且顺序和写入顺序一致
   fmt.Println("TreeMap   Keys:", treeMap.Keys())   //TreeMap   Keys: [1 2 3 4 5 6 7 8 9]
   fmt.Println("TreeMap Values:", treeMap.Values()) //TreeMap Values: [1 2 3 4 5 6 7 8 9]
   //treemap键列表和值列表也有序返回,但是不和写入顺序一致,按自然数升序返回
}
8543a037815353a6c09458e43c6e9326402800.png

通过打印结果我们可以发现:

hashmap的键列表和值列表返回值的顺序没有规律,随机返回

listmap键列表和值列表有序返回,且顺序和写入顺序一致

treemap键列表和值列表也有序返回,但是不和写入顺序一致,按自然数升序返回

这也佐证了我开篇提到的使用技巧。

为了让大家更好的理解gmap,下面介绍一下gmap的基础使用和一些进阶技巧。

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

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

对比sync.Map

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

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

goos: linux
goarch: amd64
Benchmark_GMapSet-4                     10000000               209 ns/op              15 B/op          0 allocs/op
Benchmark_SyncMapSet-4                   3000000               451 ns/op              67 B/op          3 allocs/op
Benchmark_GMapGet-4                     30000000              66.4 ns/op               0 B/op          0 allocs/op
Benchmark_SyncMapGet-4                  30000000              36.0 ns/op               0 B/op          0 allocs/op
Benchmark_GMapRemove-4                  10000000               207 ns/op               0 B/op          0 allocs/op
Benchmark_SyncMapRmove-4                30000000              42.4 ns/op               0 B/op          0 allocs/op

对性能测试感兴趣的小伙伴可以详细看下官方文档的介绍[2],不作为这篇文章的重点。

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

更多操作大家可以直接查看下方的代码示例,也欢迎大家动手实践

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

package main

import (
   "fmt"
   "github.com/gogf/gf/v2/container/gmap"
)

func main() {
   m := gmap.New(true)
   // 设置键值对
   for i := 0; i < 10; i++ {
      m.Set(i, i)
   }
   fmt.Println("查询map大小:", m.Size())

   //批量设置键值对
   m.Sets(map[interface{}]interface{}{
      10: 10,
      11: 11,
   })

   // 目前map的值
   fmt.Println("目前map的值:", m)

   fmt.Println("查询是否存在键值对:", m.Contains(1))

   fmt.Println("根据key获得value:", m.Get(1))

   fmt.Println("删除数据", m.Remove(1))

   //删除多组数据
   fmt.Println("删除前的map大小:", m.Size())
   m.Removes([]interface{}{2, 3})
   fmt.Println("删除后的map大小:", m.Size())

   //当前键名列表
   fmt.Println("键名列表:", m.Keys())   //我们发现是无序列表
   fmt.Println("键值列表:", m.Values()) //我们发现也是无序列表

   //查询键名,当键值不存在时写入默认值
   fmt.Println(m.GetOrSet(20, 20))   //返回值是20
   fmt.Println(m.GetOrSet(20, "二十")) //返回值仍然是20,因为key对应的值存在
   m.Remove(20)
   fmt.Println(m.GetOrSet(20, "二十")) //返回值是二十,因为key对应的值不存在

   // 遍历map
   m.Iterator(func(k interface{}, v interface{}) bool {
      fmt.Printf("%v:%v \n", k, v)
      return true
   })

   //自定义写锁操作
   m.LockFunc(func(m map[interface{}]interface{}) {
      m[88] = 88
   })

   // 自定义读锁操作
   m.RLockFunc(func(m map[interface{}]interface{}) {
      fmt.Println("m[88]:", m[88])
   })

   // 清空map
   m.Clear()

   //判断map是否为空
   fmt.Println("m.IsEmpty():", m.IsEmpty())
}
66abc6a790d752046c2891d784b71ccf134845.png

上面介绍的基础使用比较简单,下面介绍进阶使用。

合并 merge

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

package main

import (
   "fmt"
   "github.com/gogf/gf/v2/container/gmap"
)

func main() {
   var m1, m2 gmap.Map
   m1.Set("k1", "v1")
   m2.Set("k2", "v2")
   m1.Merge(&m2)
   fmt.Println("m1.Map()", m1.Map()) //m1.Map() map[k1:v1 k2:v2]
   fmt.Println("m2.Map()", m2.Map()) //m2.Map() map[k2:v2]
}

36469cc270892fe927a399e1c39fd1becd0ff2.png

正如之前的文章 GoFrame glist 基础使用和自定义遍历[3] 介绍的,gf框架提供的数据类型不仅支持设置并发安全开关,也都支持序列化和反序列化。

json序列化和反序列化:序列化就是转成json格式,反序列化就是json转成其他格式类型(比如:map、数组、对象等)

package main

import (
   "encoding/json"
   "fmt"
   "github.com/gogf/gf/v2/container/gmap"
)

func main() {
   // 序列化
   //var m gmap.Map
   m := gmap.New() //必须实例化 只是像上面声明但是不进行实例化,是无法序列化成功的
   m.Sets(map[interface{}]interface{}{
      "name": "王中阳",
      "age":  28,
   })
   res, _ := json.Marshal(m)
   fmt.Println("序列化结果:", string(res)) //打印结果:{"age":28,"name":"王中阳"}

   // 反序列化
   m2 := gmap.New()
   s := []byte(`{"age":28,"name":"王中阳"}`)
   _ = json.Unmarshal(s, &m2)
   fmt.Println("反序列化结果:", m2.Map()) //反序列化结果:map[age:28 name:王中阳]
}

173ef662701293dbe776349aeb8b79aac08192.png

正如上面代码段中注释掉的://var m gmap.Map

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

m := gmap.New() 
64bf58466474aa2ec49079c3a246ac25151580.png

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

var m gmap.Map
d403a3b4596ed98bcef024d7642ea6763f15d4.png

另外一个需要注意的知识点就是过滤空值了:

首先明确:空值和nil是不一样的。

nil是未定义;而空值包括空字符串,false、0等

package main

import (
   "fmt"
   "github.com/gogf/gf/v2/container/gmap"
)

func main() {
   //首先明确:空值和nil是不一样的,nil是未定义;而空值包括空字符串,false、0等
   m1 := gmap.NewFrom(map[interface{}]interface{}{
      "k1": "",
      "k2": nil,
      "k3": 0,
      "k4": false,
      "k5": 1,
   })

   m2 := gmap.NewFrom(map[interface{}]interface{}{
      "k1": "",
      "k2": nil,
      "k3": 0,
      "k4": false,
      "k5": 1,
   })

   m1.FilterEmpty()
   m2.FilterNil()

   fmt.Println("m1.FilterEmpty():", m1) //预测结果:k5:1
   fmt.Println("m2.FilterNil():", m2)   //预测结果:除了k2,其他都返回
}

07abf9f259089761c0658156841a25701cf3ef.png

还有一个非常好用的特性,键值对反转:

键值对反转 Flip

package main

import (
   "github.com/gogf/gf/v2/container/gmap"
   "github.com/gogf/gf/v2/frame/g"
)

func main() {
   // 键值对反转flip
   var m gmap.Map
   m.Sets(map[interface{}]interface{}{
      "k1": "v1",
      "k2": "v2",
   })
   fmt.Println("反转前:", m.Map())
   m.Flip()
   fmt.Println("反转后:", m.Map())
}

8706a0c6991c693afd23075d065b75e352eb3d.png

package main

import (
   "fmt"
   "github.com/gogf/gf/v2/container/gmap"
)

func main() {
   //pop pops map出栈(弹栈)
   var m gmap.Map
   m.Sets(map[interface{}]interface{}{
      1: 1,
      2: 2,
      3: 3,
      4: 4,
      5: 5,
   })

   fmt.Println("m.Pop()之前:", m.Map())
   key, value := m.Pop()
   fmt.Println("key:", key)
   fmt.Println("value:", value)
   fmt.Println("m.Pop()之后:", m.Map()) //多次测试后发现是随机出栈,不能理所当然的认为按顺序出栈

   res := m.Pops(2) //参数是出栈个数
   fmt.Println("res:", res)
   fmt.Println("m.Pops之后:", m.Map()) //多次测试之后发现也是随机出栈
}
06ce75901de4b10e44495677202eb322343d85.png

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

我们深入思考一下原因:其实很简单,因为代码示例中gmap.Map对象的底层实现是hashmap,本身就是无序的,当然不可能按顺序出栈了。

好了,我们再来回顾一下这篇文章的重点:

  • 我们在使用GoFrame的gmap时,要结合自己的场景使用合适的map容器:

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

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

当需要让返回结果按照自然升序排列时使用TreeMap;

gmap的实例化默认是HashMap​类型:hashMap := gmap.New(true)

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

[1]# Go容易搞错的知识点汇总:Go map如何实现排序 部分: https://juejin.cn/post/7131717990558466062#heading-25

[2]官方文档的介绍: https://goframe.org/pages/viewpage.action?pageId=30736719

[3]GoFrame glist 基础使用和自定义遍历: https://juejin.cn/post/7101515355062796296

本文转载自微信公众号「 程序员升级打怪之旅」,作者「王中阳Go」,可以通过以下二维码关注。

35b39ed2951268083287587ef3a508af9bf61f.jpg

转载本文请联系「 程序员升级打怪之旅」公众号。

责任编辑:武晓燕 来源: 程序员升级打怪之旅

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK