跳到主要内容

避坑-4-map多键索引

一 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的键。