前言
有好多初學GO的小伙伴都被Go語言中map的無序性“坑過”。尤其是PHP轉(zhuǎn)Go的小伙伴~
這篇文章會為大家介紹:
GoFrame的gmap相比于Go原生的map有什么優(yōu)勢?為什么天然支持排序和有序遍歷?如何做到的?
GoFrame的gmap有哪些使用技巧?
先說結(jié)論
GoFrame提供的gmap字典類型,包含多個數(shù)據(jù)結(jié)構(gòu)的map?容器:HashMap、TreeMap和ListMap?。其中TreeMap?支持排序,TreeMap和ListMap支持有序遍歷。
使用技巧
我們在使用GoFrame的gmap時,要結(jié)合自己的場景使用合適的map容器:
- 當我們對返回順序有要求時不能使用HashMap?,因為HashMap返回的是無序列表;
- 當需要按輸入順序返回結(jié)果時使用ListMap;
- 當需要讓返回結(jié)果按照自然升序排列時使用TreeMap;
注意:gmap的實例化默認是HashMap?類型:hashMap := gmap.New(true)?
一圖勝千言
GoFrame gmap 基本介紹:
支持并發(fā)安全開關(guān)選項的map容器,最常用的數(shù)據(jù)結(jié)構(gòu)。
該模塊包含多個數(shù)據(jù)結(jié)構(gòu)的map?容器:HashMap、TreeMap和ListMap。

實例化示例:
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]
//從打印結(jié)果可知hashmap的鍵列表和值列表返回值的順序沒有規(guī)律,隨機返回
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鍵列表和值列表也有序返回,但是不和寫入順序一致,按自然數(shù)升序返回
}
打印結(jié)果

通過打印結(jié)果我們可以發(fā)現(xiàn):
hashmap的鍵列表和值列表返回值的順序沒有規(guī)律,隨機返回
listmap鍵列表和值列表有序返回,且順序和寫入順序一致
treemap鍵列表和值列表也有序返回,但是不和寫入順序一致,按自然數(shù)升序返回
這也佐證了我開篇提到的使用技巧。
為了讓大家更好的理解gmap,下面介紹一下gmap的基礎(chǔ)使用和一些進階技巧。
基礎(chǔ)概念
GoFrame框架(下文簡稱gf)提供的數(shù)據(jù)類型,比如:字典gmap、數(shù)組garray、集合gset、隊列g(shù)queue、樹形結(jié)構(gòu)gtree、鏈表glist都是支持設(shè)置并發(fā)安全開關(guān)的。
支持設(shè)置并發(fā)安全開關(guān)這也是gf提供的常用數(shù)據(jù)類型和原生數(shù)據(jù)類型重要的區(qū)別之一。
對比sync.Map
Go語言提供的原生map不是并發(fā)安全的map類型
Go語言從1.9版本開始引入了并發(fā)安全的sync.Map,但gmap比較于標準庫的sync.Map性能更加優(yōu)異,并且功能更加豐富。
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],不作為這篇文章的重點。
基礎(chǔ)使用
- gmap.New(true) 在初始化的時候開啟并發(fā)安全開關(guān)
- 通過 Set() 方法賦值,通過 Sets() 方法批量賦值
- 通過 Size() 方法獲取map大小
- 通過 Get() 根據(jù)key獲取value值
- ...
更多操作大家可以直接查看下方的代碼示例,也歡迎大家動手實踐
為了方便大家更好的查看效果,在下方代碼段中標明了打印結(jié)果:
package main
import (
"fmt"
"github.com/gogf/gf/v2/container/gmap"
)
func main() {
m := gmap.New(true)
// 設(shè)置鍵值對
for i := 0; i < 10; i++ {
m.Set(i, i)
}
fmt.Println("查詢map大小:", m.Size())
//批量設(shè)置鍵值對
m.Sets(map[interface{}]interface{}{
10: 10,
11: 11,
})
// 目前map的值
fmt.Println("目前map的值:", m)
fmt.Println("查詢是否存在鍵值對:", m.Contains(1))
fmt.Println("根據(jù)key獲得value:", m.Get(1))
fmt.Println("刪除數(shù)據(jù)", m.Remove(1))
//刪除多組數(shù)據(jù)
fmt.Println("刪除前的map大?。?, m.Size())
m.Removes([]interface{}{2, 3})
fmt.Println("刪除后的map大?。?, m.Size())
//當前鍵名列表
fmt.Println("鍵名列表:", m.Keys()) //我們發(fā)現(xiàn)是無序列表
fmt.Println("鍵值列表:", m.Values()) //我們發(fā)現(xiàn)也是無序列表
//查詢鍵名,當鍵值不存在時寫入默認值
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())
}
運行結(jié)果

上面介紹的基礎(chǔ)使用比較簡單,下面介紹進階使用。
合并 merge
注意:Merge()的參數(shù)需要是map的引用類型,也就是參數(shù)需要傳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]
}
打印結(jié)果

序列化
正如之前的文章 GoFrame glist 基礎(chǔ)使用和自定義遍歷[3] 介紹的,gf框架提供的數(shù)據(jù)類型不僅支持設(shè)置并發(fā)安全開關(guān),也都支持序列化和反序列化。
json序列化和反序列化:序列化就是轉(zhuǎn)成json格式,反序列化就是json轉(zhuǎn)成其他格式類型(比如:map、數(shù)組、對象等)
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("序列化結(jié)果:", string(res)) //打印結(jié)果:{"age":28,"name":"王中陽"}
// 反序列化
m2 := gmap.New()
s := []byte(`{"age":28,"name":"王中陽"}`)
_ = json.Unmarshal(s, &m2)
fmt.Println("反序列化結(jié)果:", m2.Map()) //反序列化結(jié)果:map[age:28 name:王中陽]
}
打印結(jié)果

踩坑
正如上面代碼段中注釋掉的://var m gmap.Map
在進行序列化操作時,必須實例化map

只是聲明map而不進行實例化,是無法序列化成功的

另外一個需要注意的知識點就是過濾空值了:
過濾空值
首先明確:空值和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) //預測結(jié)果:k5:1
fmt.Println("m2.FilterNil():", m2) //預測結(jié)果:除了k2,其他都返回
}
打印結(jié)果

還有一個非常好用的特性,鍵值對反轉(zhuǎn):
鍵值對反轉(zhuǎn) Flip
package main
import (
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
// 鍵值對反轉(zhuǎn)flip
var m gmap.Map
m.Sets(map[interface{}]interface{}{
"k1": "v1",
"k2": "v2",
})
fmt.Println("反轉(zhuǎn)前:", m.Map())
m.Flip()
fmt.Println("反轉(zhuǎn)后:", m.Map())
}
打印結(jié)果

出棧
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()) //多次測試后發(fā)現(xiàn)是隨機出棧,不能理所當然的認為按順序出棧
res := m.Pops(2) //參數(shù)是出棧個數(shù)
fmt.Println("res:", res)
fmt.Println("m.Pops之后:", m.Map()) //多次測試之后發(fā)現(xiàn)也是隨機出棧
}
運行結(jié)果

踩坑
注意:多次測試后發(fā)現(xiàn)是隨機出棧,不能理所當然的認為按順序出棧。
我們深入思考一下原因:其實很簡單,因為代碼示例中g(shù)map.Map對象的底層實現(xiàn)是hashmap,本身就是無序的,當然不可能按順序出棧了。
總結(jié)
好了,我們再來回顧一下這篇文章的重點:
- 我們在使用GoFrame的gmap時,要結(jié)合自己的場景使用合適的map容器:
當我們對返回順序有要求時不能使用HashMap?,因為HashMap返回的是無序列表;
當需要按輸入順序返回結(jié)果時使用ListMap;
當需要讓返回結(jié)果按照自然升序排列時使用TreeMap;
gmap的實例化默認是HashMap?類型:hashMap := gmap.New(true)
- gmap的基礎(chǔ)使用和進階使用技巧:反轉(zhuǎn)map、序列化、合并map、出棧等。
- gf框架提供的數(shù)據(jù)結(jié)構(gòu),比如:字典gmap、數(shù)組garray、集合gset、隊列g(shù)queue、樹形結(jié)構(gòu)gtree、鏈表glist都是支持設(shè)置并發(fā)安全開關(guān)的;而且都支持序列化和反序列化,實現(xiàn)了標準庫json數(shù)據(jù)格式的序列化/反序列化接口。
相關(guān)文章
[1]# Go容易搞錯的知識點匯總:Go map如何實現(xiàn)排序 部分: https://juejin.cn/post/7131717990558466062#heading-25
[2]官方文檔的介紹: https://goframe.org/pages/viewpage.action?pageId=30736719
[3]GoFrame glist 基礎(chǔ)使用和自定義遍歷: https://juejin.cn/post/7101515355062796296
本文轉(zhuǎn)載自微信公眾號「 程序員升級打怪之旅」,作者「王中陽Go」,可以通過以下二維碼關(guān)注。

轉(zhuǎn)載本文請聯(lián)系「 程序員升級打怪之旅」公眾號。