我也在B站发布了这期内容的视频版,视频相比文章看起来确实更通俗易懂。 假如你是初学者建议先看视频:欢迎咱们点击这个链接观看。 觉得不错,欢迎关注、三连一波。谢谢!

假如你有经验,想节省时间,请直接阅读文章:

前言

有很多初学GO和GoFrame的小伙伴搞不清楚map怎么用。

不少刚入门的小伙伴都被Go言语中map的无序性“坑过”,尤其是PHP转Go的小伙伴,毕竟用惯了PHP的数组。

这篇文章便是给初学的小伙伴们答疑解惑的,会为咱们介绍:

为什么Go言语中的map是无序的,怎么自定义完成map的排序?

(Ps:这部分不作为这篇文章的要点,感兴趣的小伙伴能够看我之前收拾的这篇文章:# Go容易搞错的知识点汇总:Go map怎么完成排序 部分)

GoFrame的gmap相比于Go原生的map有什么优势?为什么天然支撑排序和有序遍历!?

先说结论

GoFrame供给的gmap字典类型,包含多个数据结构的map容器:HashMapTreeMapListMap。其中TreeMap支撑排序,TreeMapListMap支撑有序遍历。

运用技巧

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

  1. 当咱们对回来次序有要求时不能运用HashMap,由于HashMap回来的是无序列表;
  2. 当需要按输入次序回来成果时运用ListMap
  3. 当需要让回来成果依照天然升序摆放时运用TreeMap

留意:gmap的实例化默许是HashMap类型:hashMap := gmap.New(true)

一图胜千言

GoFrame gmap 基本介绍:

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

该模块包含多个数据结构的map容器:HashMapTreeMapListMap

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

实例化示例:

   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键列表和值列表也有序回来,但是不好写入次序共同,按天然数升序回来
}

打印成果

通过打印成果咱们能够发现:

  1. hashmap的键列表和值列表回来值的次序没有规则,随机回来

  2. listmap键列表和值列表有序回来,且次序和写入次序共同

  3. treemap键列表和值列表也有序回来,但是不好写入次序共同,按天然数升序回来

这也佐证了我开篇说到的运用技巧。

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

为了让咱们更好的理解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

对功能测试感兴趣的小伙伴能够详细看下官方文档的介绍,不作为这篇文章的要点。

根底运用

  1. gmap.New(true) 在初始化的时分敞开并发安全开关
  2. 通过 Set() 办法赋值,通过 Sets() 办法批量赋值
  3. 通过 Size() 办法获取map巨细
  4. 通过 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())
}

运转成果

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

上面介绍的根底运用比较简单,下面介绍进阶运用。

兼并 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]
}

打印成果

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

序列化

正如之前的文章 GoFrame glist 根底运用和自定义遍历 介绍的,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:王中阳]
}

打印成果

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

踩坑

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

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

m := gmap.New()

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

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

var m gmap.Map

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

别的一个需要留意的知识点便是过滤空值了:

过滤空值

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,其他都回来
   // 打印成果和预期的共同:
   //m1.FilterEmpty(): {"k5":1}
   //m2.FilterNil(): {"k1":"","k3":0,"k4":false,"k5":1}
}

打印成果

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

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

键值对回转 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())
}

打印成果

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

出栈(随机出栈)

这个出栈的知识点和我开篇的运用技巧呼应上了:

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()) //多次测试之后发现也是随机出栈
}

运转成果

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

踩坑

留意:多次测试后发现是随机出栈,不能天经地义的以为按次序出栈。

咱们深化考虑一下原因:其实很简单,由于gmap的底层完成是hashmap,自身便是无序的,当然不可能按次序出栈了。

总结

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

  1. 咱们在运用GoFrame的gmap时,要结合自己的场景运用适宜的map容器:
    1. 当咱们对回来次序有要求时不能运用HashMap,由于HashMap回来的是无序列表;
    2. 当需要按输入次序回来成果时运用ListMap
    3. 当需要让回来成果依照天然升序摆放时运用TreeMap
    4. gmap的实例化默许是HashMap类型:hashMap := gmap.New(true)
  2. gmap的根底运用和进阶运用技巧:回转map、序列化、兼并map、出栈等。
  3. gf结构供给的数据结构,比如:字典gmap、数组garray、调集gset、行列gqueue、树形结构gtree、链表glist 都是支撑设置并发安全开关的;而且都支撑序列化和反序列化,完成了规范库json数据格局的序列化/反序列化接口。

一起学习

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

公众号:程序员升职加薪之旅

微信号:wangzhongyang1993

B站视频:王中阳Go