复合数据类型(二)map 及其底层实现

154 阅读3分钟

map

map[key_type]value_type

map[string]string // key与value元素的类型相同
map[int]string    // key与value元素的类型不同

删除

内建函数delete

增加/修改

map[key] = value

获取键值对数量

内建函数len(map)

遍历

for range 每次遍历的顺序不一致,k,v均可单独遍历 【知识碎片】 key类型必须支持==!= 两种比较操作符,而函数类型、map类型,切片类型只支持nil,不支持同类型比较,所以不能作为map的key值

声明与初始化

// 声明
var m map[string]int
map["str"] = 1 //运行异常,必须初始化后再使用

// 初始化方式一:复合字面值
m := map[int]string{} // 有大括号 不等同于nil值,可以操作
m := mapmap[int][]string{
1:[]string{"1","2"},
2:{"2","3"}
}

// 初始化方式二:内建函数make
m := make(map[int]string) // 未指定初始容量
m := make(map[int]string, 6) // 指定初始容量为6

切片被赋予零值nil时,仍可以使用append函数,go中称为这叫零值可用,而map不行,如果不初始化 会导致运行时panic 若查找数据不存在,则会返回value元素类型的零值,使用 comma ok 查找数据,否则可能无法判断是否有改元素

comma ok

func main() {
	// make(数组类型,长度,容量)
	m := make(map[string]int)
	_, ok := m["key1"]
	if !ok {
		fmt.Println("v is nil")
	}

	m["key1"] = 1
	v1, ok := m["key1"]
	if !ok {
		fmt.Println("v is nil")
	}
	fmt.Println("v1: ", v1)

}

map 底层实现

Go 编译器会将 Go 语法层面的 map 操作,重写成运行时对应的函数调用。

【注】以下代码引用 《Tony Bai · Go 语言第一课》内容


// 创建map类型变量实例
m := make(map[keyType]valType, capacityhint) → m := runtime.makemap(maptype, capacityhint, m)

// 插入新键值对或给键重新赋值
m["key"] = "value" → v := runtime.mapassign(maptype, m, "key") v是用于后续存储value的空间的地址

// 获取某键的值 
v := m["key"]      → v := runtime.mapaccess1(maptype, m, "key")
v, ok := m["key"]  → v, ok := runtime.mapaccess2(maptype, m, "key")

// 删除某键
delete(m, "key")   → runtime.mapdelete(maptype, m, “key”)

运行时表示

type hmap struct {
    // 元素个数,调用 len(map) 时,直接返回此值
	count     int
	flags     uint8
	// buckets 的对数 log_2
	B         uint8
	// overflow 的 bucket 近似数
	noverflow uint16
	// 计算 key 的哈希的时候会传入哈希函数
	hash0     uint32
    // 指向 buckets 数组,大小为 2^B
    // 如果元素个数为0,就为 nil
	buckets    unsafe.Pointer
	// 等量扩容的时候,buckets 长度和 oldbuckets 相等
	// 双倍扩容的时候,buckets 长度会是 oldbuckets 的两倍
	oldbuckets unsafe.Pointer
	// 指示扩容进度,小于此地址的 buckets 迁移完成
	nevacuate  uintptr
	extra *mapextra // optional fields
}

  • map初始化后,在go runtime中会调用makemap函数 返回一个hmap类型的指针,hmap就是实际运行时的map。
  • hmap.buckets 指向实际存数据的buckets[N]数据,buckets[i]有tophash[8]数组,8个key值,如果超出且未达到buckets扩容则末尾用overflow指针外接一个结构类似的bukets,成为overflow bukets。

map get过程

  • 根据机器位数,如64获取一个64位的二进制数。
  • 按hmap的B的值,如果B=5,则去上述64位数的后五位,定位buket[i]位置。
  • 取64高8位作为top值,然后遍历buket[i]中tophash数组,该数组大小也为8,top = tophash[i]时,当前top值为地址,利用i 取地址得到key值,看是否等于map get传过来的key值,相等则利用i 获得value的地址,然后返回去地址后的值。