一 map多键索引
1.0 需求示例
示例如下:
package main
type Profile struct{
    Name string
    Age int
    Married bool
}
func main() {
    list := []*Profile{
        {
            Name: "zs",
            Age: 30,
            Married: true,
        },
        {
            Name: "ls",
            Age: 15,
            Married: false,
        },
        {
            Name: "ww",
            Age: 60,
            Married: false,
        },
    }
    buildIndex(list)            // 生成索引函数
    queryData("ww", 60)         // 实现查询方法:按照名字和年龄
}
本示例需要用算法实现 buildlndex() 构建索引函数及 queryData() 查询数据函数。
1.1 方式一:基于哈希值的多键索引
传统的数据索引过程是将输入的数据做特征值,这种特征值有几种常见做法:
- 使用某种算法将特征转换为整数,即哈希值,使用整型值做索引
 - 将特征转为字符串,使用字符串做索引
 
/**
    字符串转哈希值方法:这里将查询键的每一个字段转换为整数,生成的特征值也不一定唯一,因为可能会出现碰撞,不过这里只是一个举例
 */
// 查询键
type classicQueryKey struct {                    
    Name string
    Age int
}
// 生成特征值的方法
func (c *classicQueryKey)buildHash() int {        
    var ret int
    for i := 0; i < len(c.Name); i++ {
        c := c.Name[i]
        ret += int(c)                            // ASCII码相加
    }
    return ret + c.Age*1000000
}
// 创建哈希值到数据的索引关系
var mapper = make(map[int][]*Person)
// 构建数据索引
func buildIndex(list []*Person) {                
    for _, p := range list {
        key := classicQueryKey{p.Name, p.Age}    // 构建数据的查询索引
        existValue := mapper[key.buildHash()]
        existValue = append(existValue, p)
        mapper[key.buildHash()] = existValue
    }
}
func queryData(name string, age int) {
    keyToQuery := classicQueryKey{name , age}
    resultList := mapper[keyToQuery.buildHash()]
    for _, result := range resultList {
        if result.Name == name && result.Age == age {
            fmt.Println(result)
            return
        }
    }
    fmt.Println("not found")
}
这种多键的算法就是哈希算法。 map 的多个元素对应哈希的“桶 ” 。哈希函数的选择决定桶的映射好坏,如果哈希冲撞很厉害,那么就需要将发生冲撞的相同哈希值的元素使用切片保存起来。
1.2 方式二:利用map的多键索引
使用结构体进行多键索引和查询比传统的写法更为简单 , 最主要的区别是无须准备哈希函数及相应的宇段无须做哈希合并。
type classicQueryKey struct {                    // 查询键
    Name string
    Age int
}
var mapper = make(map[classicQueryKey]*Person)
func buildIndex(list []*Person) {                // 构建数据索引
    for _, p := range list {
        key := classicQueryKey{p.Name, p.Age}    // 构建数据的查询索引
        mapper[key] = p
    }
}
func queryData(name string, age int) {
    keyToQuery := classicQueryKey{name , age}
    result, ok := mapper[keyToQuery]
    if ok {
        fmt.Println(result)
    } else {
        fmt.Println("not found")
    }
}
解释:基于哈希值的多键索引查询和利用 map 特性的多键索引查询的代码复杂程度显而易见。其实,利用 map 特性的例子中的 map 类型即便修改为下面的格式,也 一样可以获得同样的结果:
var mapper = make(map[interface{})*Person)
代码量大大减少的关键是: Go语言的底层会为 map 的键自动构建哈希值。
能够构建哈希值的类型必须是非动态类型、非指针、函 数 、闭包 。
- 非动态类型 : 可用数组,不能用切片。
 - 非指针: 每 个指针数值都不同, 失去哈希意义。
 - 函数、闭包不能作为 map的键。