本文针对 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种方式都有使用到。