哈希表2

92 阅读3分钟

如何实现get

  1. 分为两种:带 comma 和 不带 comma 就是带ok不带ok
package main

import "fmt"

func main() {
	ageMap := make(map[string]int)
	ageMap["qcrao"] = 18

    // 不带 comma 用法
	age1 := ageMap["stefno"]
	fmt.Println(age1)

    // 带 comma 用法
	age2, ok := ageMap["stefno"]
	fmt.Println(age2, ok)
}

遍历过程

复赋值过程

对 key 计算 hash 值,根据 hash 值按照之前的流程,找到要赋值的位置(可能是插入新 key,也可能是更新老 key),对相应位置进行赋值。

删除过程

扩容过程

  1. Go 语言采用一个 bucket 里装载 8 个 key,定位到某个 bucket 后,还需要再定位到具体的 key,这实际上又用了时间换空间。

    当然,这样做,要有一个度,不然所有的 key 都落在了同一个 bucket 里,直接退化成了链表,各种操作的效率直接降为 O(n),是不行的。

    因此,需要有一个指标来衡量前面描述的情况,这就是装载因子。Go 源码里这样定义 装载因子 count / (2^B)

    count 就是 map 的元素个数,2^B 表示 bucket 数量。

  2. 扩容条件:装载因子超过阈值;overflow的bucket的数量过多(当 B 小于 15,也就是 bucket 总数 2^B 小于 2^15 时,如果 overflow 的 bucket 数量超过 2^B;当 B >= 15,也就是 bucket 总数 2^B 大于等于 2^15,如果 overflow 的 bucket 数量超过 2^15。)

image.png

image.png 3. 解决方法:a是房子太少,住户太多,那就就让B加一,新 bucket 只是最大数量变为原来最大数量(2^B)的 2 倍(2^B * 2)。

b.是房子太多人太少。解决办法就是开辟一个新 bucket 空间,将老 bucket 中的元素移动到新 bucket,使得同一个 bucket 中的 key 排列地更紧密。 变迁是一个渐进过程,不会一次完成。

key为什么无序?

  1. 扩容后key的位置发生改变

  2. go规定遍历每次都是从随机bucket的水机cell开始遍历

float 型可以作为 key,但是由于精度的问题,会导致一些诡异的问题,慎用之。

可以边遍历边删除嘛?

  1. 多个协程同时读写同一个 map, map 并不是一个线程安全的数据结构。同时读写一个 map 是未定义的行为,如果被检测到,会直接 panic。

  2. 如果在同一个协程内边遍历边删除,并不会检测到同时读写,理论上是可以这样做的。但是,遍历的结果就可能不会是相同的了,有可能结果遍历结果集中包含了删除的 key,也有可能不包含,这取决于删除 key 的时间:是在遍历到 key 所在的 bucket 时刻前或者后。

image.png

不可以对map的key 和value取地址

俩map相等

image.png

map不是线程安全的。

在查找、赋值、遍历、删除的过程中都会检测写标志,一旦发现写标志置位(等于1),则直接 panic。赋值和删除函数在检测完写标志是复位之后,先将写标志位置位,才会进行之后的操作。