开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
名词解释
-
哈希表:是一种实现关联数组抽象数据类型的数据结构,这种结构可以将关键码映射到给定值。简单来说哈希表(key-value)之间存在一个映射关系,是键值对的关系,一个键对应一个值。
-
哈希冲突:当两个不同的数经过哈希函数计算后得到了同一个结果,即他们会被映射到哈希表的同一个位置时,即称为发生了哈希冲突。简单来说就是哈希函数算出来的地址被别的元素占用了。
哈希冲突的四种解决方法
链式地址法(拉链法)
将所有哈希地址相同的记录都链接在同一链表中。
再哈希法
同时构造多个不同的哈希函数,等发生哈希冲突时就使用第二个、第三个……等其他的哈希函数计算地址,直到不发生冲突为止。虽然不易发生聚集,但是增加了计算时间。
线性探测法
发生冲突,往后加1一直到空闲位置
二次探测
冲突发生时,在表的左右进行跳跃式探测,比较灵活。
应用
golang map 中的哈希
-
golang里的map是一个kv对集合。底层使用hash table,用链表来解决冲突。出现冲突时,以bmap为最小粒度挂载,一个bmap可以放8个kv。哈希函数选择,可以支持aes采取aes,否则使用memhash。
-
bucket:8个大小,data的byte字段存储kv的值,节省字节对齐带来的空间浪费;overflow记录溢出的bucket地址,将所有冲突的键连接起来。
-
哈希冲突:链地址法。overflow指向了一处部分,降低了存储效率。
-
负载因子的含义和影响:
-
含义:衡量一个哈希表冲突的情况,负载因子 = 键数量/bucket数量
-
哈希表需要将负载因子控制在合适的大小,超过其阈值的需要进行rehash
- 过小说明空间利用率低;过大说明冲突严重,存取效率低
- 哈希表实现的不同导致容忍度不同,redis的负载因子时1就出发rehash,go由于bucket可以装8个键值对,所以可以容忍更高的负载因子6.5
-
-
map扩容:
-
负载因子 > 6.5时,也即平均每个bucket存储的键值对达到6.5个。
- 当负载因子过大时,就新建一个bucket,新的bucket长度是原来的2倍,采用渐进式reshash,每次搬迁2个键值对。
-
overflow数量 > 2^15时,也即overflow数量超过32768时。
-
等量扩容:buckets数量不变,重新做一遍类似增量扩容的搬迁动作,吧松散的键值对重新排列一次,以使bucket的利用率更高,保证更快的存取
-
redis hash冲突
-
使用链式哈希来解决,即同一个哈希桶的多个元素用一个链表来保存,它们之间依次用指针连接
- 数据量增大,链表长度增长,查询速度变慢
-
对哈希表做rehash操作,增加现有的哈希桶数量。Redis维护了两个全局哈希表。
- rehash步骤1:给哈希表2分配更大的空间
- rehash步骤2:把哈希表1中的数据重新映射并拷贝到哈希表2中
- rehash步骤3:释放哈希表1的空间
- 缺点:涉及到大量数据拷贝,会造成线程阻塞,无法服务其他请求
-
渐进式rehash:第二步拷贝数据时,redis仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;将一次性大量的拷贝开销,分摊到了多次处理请求中,避免耗时操作,保证数据的快速访问