本文针对 Golang 的 Map 实现几个简单示例。这些都是在实际工程中使用到的。
基本使用
map 是一种无序的基于key-value的数据结构,Golang 的 map 是引用类型,因此必须初始化才能使用。
下面给出几种初始化形式示例:
var m map[string]int
m = make(map[string]int)
m := make(map[string]int)
var FuncMap = map[string]func(int, string){
...
}
var allMap map[string]PersonInfo_t
allMap = make(map[string]PersonInfo_t)
增加:
m["latelee"] = 250
删除:
delete(m, "latelee")
查询 key 是否存在:
if v, ok := allMap[id]; ok {
...
}
遍历:
for k, v := range allMap {
...
}
实践
对基本使用有一定了解后,需根据实际应用场合使用,本节列举几个示例:
函数调用
需求:一个命令模块,根据不同的命令名称,调用不同的函数。
ifFuncMap
// 全局变量,映射,可由其它包使用
// 简单定义map的函数指针
var FuncMap = map[string]func(int, string){
"111": map1,
"222": map2,
}
func map1(a int, b string) {
fmt.Println("111", a, b)
}
func map2(a int, b string) {
fmt.Println("222", a, b)
}
func test1(id string) {
a := 250
b := "25.250"
if fn, ok := FuncMap[id]; ok {
fn(a, b)
} else {
fmt.Println(id, "not found func")
}
}
输出结果:
111 250 25.250
222 250 25.250
333 not found func
多线程的使用
sync.Map
g_syncMap.Store
g_syncMap.Delete(key)
测试代码如下:
// sync.Map使用
var g_syncMap sync.Map
func showSyncMap() {
// 遍历打印,不做死循环
g_syncMap.Range(func(k, v interface{}) bool {
v1 := v.(string)
fmt.Printf("key: %v -> %v\n", k, v1)
return true
})
}
func test2() {
// 存
g_syncMap.Store(1, "111")
g_syncMap.Store(2, "222")
g_syncMap.Store(3, "333")
showSyncMap()
// 删除
g_syncMap.Delete(1)
showSyncMap()
g_syncMap.Store(3, "333_111") // 重复写同一个key,会更新为新的
showSyncMap()
//Load 方法,获得value
if v, ok := g_syncMap.Load(2); ok {
fmt.Println("Load ->", v)
}
//LoadOrStore方法,获取或者保存
//参数是一对key:value,如果该key存在且没有被标记删除则返回原先的value(不更新)和true;
// 不存在则store,返回该value 和false
if vv, ok := g_syncMap.LoadOrStore(1, "c"); !ok { // 前面删除1了,这里重新保存
fmt.Println("save new", vv)
}
if vv, ok := g_syncMap.LoadOrStore(2, "c"); ok { // 2 一直存在
fmt.Println("exist", vv, ok)
}
showSyncMap()
}
输出结果如下:
key: 1 -> 111
key: 2 -> 222
key: 3 -> 333
key: 2 -> 222
key: 3 -> 333
key: 2 -> 222
key: 3 -> 333_111
Load -> 222
save new c
exist 222 true
key: 1 -> c
key: 2 -> 222
key: 3 -> 333_111
多 key 单独查询
需求:某种数据有很多个字段,可以根据其中的一些字段查询的对象。如某一人员信息,有ID、code代码、名称等,可以根据ID查人员信息,也可以根据code代码查,还可以根据名称查(假定名称不重复)。
由于 map 不能有多个 key,因此使用多个 map 映射。设主信息的 allMap 的 key 为 ID,value 为人员信息。另外有若干辅助 map,cMap 的 key 为 code,value为ID;hMap类似,key 为 hex 码,value 为 ID。查询时,先查辅助 map,得到 ID值,再查主 map。
示例代码:
type PersonInfo_t struct {
id string
hex string
code string
name string
age int
}
var allMap map[string]PersonInfo_t
var cMap map[string]string
var hMap map[string]string
func findFromItem(item string) (ret PersonInfo_t) {
id := item // default id
if len(item) == 4 { // hex
id = hMap[item]
} else if len(item) == 6 { // code
id = cMap[item]
}
if v, ok := allMap[id]; ok {
fmt.Printf("found %v\n", id)
return v
}
return PersonInfo_t{}
}
func showMap(allMap map[string]PersonInfo_t) {
var keys []string
for k, _ := range allMap {
keys = append(keys, k)
}
// 进行数组的排序
sort.Strings(keys)
// 遍历数组就是有序的了
for _, k := range keys {
fmt.Printf("v: %v\n", allMap[k])
}
}
func multiMap() {
allMap = make(map[string]PersonInfo_t)
cMap = make(map[string]string)
hMap = make(map[string]string)
for i := 0; i < 10; i++ {
var tmp PersonInfo_t
tmp.id = fmt.Sprintf("id_%02d", i)
tmp.code = fmt.Sprintf("C00%03d", i)
tmp.hex = fmt.Sprintf("H%03d", i)
tmp.name = fmt.Sprintf("foo_%d_bar", i)
tmp.age = 20
// 总的
allMap[tmp.id] = tmp
// 存储id的
cMap[tmp.code] = tmp.id
hMap[tmp.hex] = tmp.id
}
//showMap(allMap)
id := "id_00"
fmt.Printf("%v: %v\n", id, allMap[id])
id = "id_100"
fmt.Printf("%v: %v\n", id, allMap[id])
id = "H007"
fmt.Printf("%v: %v\n", id, findFromItem(id))
id = "H017"
fmt.Printf("%v: %v\n", id, findFromItem(id))
id = "C00005"
fmt.Printf("%v: %v\n", id, findFromItem(id))
}
输出结果:
id_00: {id_00 H000 C00000 foo_0_bar 20}
id_100: { 0}
found id_07
H007: {id_07 H007 C00007 foo_7_bar 20}
H017: { 0}
found id_05
C00005: {id_05 H005 C00005 foo_5_bar 20}
多 key 查询
需求:某种数据有很多个字段,由其中的两个字段共同决定查询的对象。接上一例子,假定必须有code代码和hex代码才能确认人员信息。在设计上,将这两个字段合并作为key即可。
代码如下:
var mMap map[string]PersonInfo_t
func findFromM(code, hex string) (ret PersonInfo_t) {
key := fmt.Sprintf("%v_%v", code, hex)
if v, ok := mMap[key]; ok {
return v
}
return PersonInfo_t{}
}
func mkMap() {
mMap = make(map[string]PersonInfo_t)
for i := 0; i < 10; i++ {
var tmp PersonInfo_t
tmp.id = fmt.Sprintf("id_%02d", i)
tmp.code = fmt.Sprintf("C00%03d", i)
tmp.hex = fmt.Sprintf("H%03d", i)
tmp.name = fmt.Sprintf("foo_%d_bar", i)
tmp.age = 20
// 多个key
key := fmt.Sprintf("%v_%v", tmp.code, tmp.hex)
mMap[key] = tmp
}
showMap(allMap)
code := "C00000"
hex := "H000"
fmt.Printf("%v & %v: %v\n", code, hex, findFromM(code, hex))
code = "C00000"
hex = "H111" // 不存在的
fmt.Printf("%v & %v: %v\n", code, hex, findFromM(code, hex))
}
输出结果:
C00000 & H000: {id_00 C00000 H000 foo_0_bar 20}
C00000 & H111: { 0}
小结
对于多 key 单独查询的场合,是典型的用空间换时间的思路,如果使用时间换空间,则可建立数组,遍历查询,查询条件即为不同的字段,数量在万级别以下,可能没有明显差别,但到百万级别,耗时就比较大了,因此,是否真正需要应用 map,还得自行评估。实际上,笔者2种方式都有使用到。