python dict的hashmap及hash冲突

在3.7之前,dict为无序的,在3.7(含)之后,dict为有序
详情可见:

https://docs.python.org/3.7/whatsnew/3.7.html

在这里插入图片描述

3.6之前的无序字典
字典底层是维护一张哈希表(见下图),我们可以把哈希表看成一个列表,哈希表中的每一个元素又存储了哈希值(hash)、键(key)、值(value)3个元素。(Python3.7之前)

enteies = [
    ['--', '--', '--'],
    [hash, key, value],
    ['--', '--', '--'],
    ['--', '--', '--'],
    [hash, key, value],
]

由上可以见哈希表的存储结构,我们也可以看出,元素之间有一些空元素,我们通过增加一个元素来讲解具体实现。

1.计算key的hash值【hash(key)】,再和mask做与操作【mask=字典最小长度(DictMinSize) - 1】,运算后会得到一个数字【index】,这个index就是要插入的enteies哈希表中的下标位置
2.若index下标位置已经被占用,则会判断enteies的key是否与要插入的key是否相等
3.如果key相等就表示key已存在,则更新value值
如果key不相等,就表示hash冲突,则会继续向下寻找空位置,一直到找到剩余空位为止。

# 给字典添加一个值,key为hello,value为word
my_dict['hello'] = 'word'

# 假设是一个空列表,hash表初始如下
enteies = [
    ['--', '--', '--'],
    ['--', '--', '--'],
    ['--', '--', '--'],
    ['--', '--', '--'],
    ['--', '--', '--'],
]

hash_value = hash('hello')  # 假设值为 12343543 注:以下计算值不等于实际值,仅为演示使用
index = hash_value & ( len(enteies) - 1)  # 假设index值计算后等于3,具体的hash算法本文不做介绍

# 下面会将值存在enteies中
enteies = [
    ['--', '--', '--'],
    ['--', '--', '--'],
    ['--', '--', '--'],
    [12343543, 'hello', 'word'],  # index=3
    ['--', '--', '--'],
]

# 我们继续向字典中添加值
my_dict['color'] = 'green'

hash_value = hash('color')  # 假设值为 同样为12343543
index = hash_value & ( len(enteies) - 1)  # 假设index值计算后同样等于3

# 下面会将值存在enteies中
enteies = [
    ['--', '--', '--'],
    ['--', '--', '--'],
    ['--', '--', '--'],
    [12343543, 'hello', 'word'],  # 由于index=3的位置已经被占用,且key不一样,所以判定为hash冲突,继续向下寻找
    [12343543, 'color', 'green'],  # 找到空余位置,则保存
]

通过上面的讲解,已经了解了字典的插入的过程,可以更具此过程分析出字典查找、插入的执行过程,这里就不过多赘述。我们可以看到,不同的key计算的出的index值是不一样的,在enteies中插入的位置不一样,所以当我们遍历字典的时候,字段的顺序与我们插入的顺序是不相同的。

我们同样可以发现,enteies表是稀疏的,随着我们插入的值不同,enteies表会越来越稀疏(enteies也是一个会动态扩展长度的,每一此扩展长度,都会重新计算所有key的hash值),所以新的字典实现就随之出现。

3.7+后的新的实现方式
老字典使用一张hash,而新字典还使用了一张Indices表来辅助。下来列出新的结构:

indices = [None, None, index, None, index, None, index]
enteies = [
    [hash0, key0, value0],
    [hash1, key1, value1],
    [hash2, key2, value2]
]

下面为具体的实现过程:

1.计算key的hash值【hash(key)】,再和mask做与操作【mask=字典最小长度(IndicesDictMinSize) - 1】,运算后会得到一个数字【index】,这个index就是要插入的indices的下标位置(注:具体算法与Python版本相关,并不一定一样)
2.得到index后,会找到indices的位置,但是此位置不是存的hash值,而是存的len(enteies),表示该值在enteies中的位置
3.如果出现hash冲突,则处理方式与老字典处理方式类似
下面带入实际实现过程:

# 给字典添加一个值,key为hello,value为word
my_dict['hello'] = 'word'

# 假设是一个空列表,hash表初始如下
indices = [None, None, None, None, None, None]
enteies = []

hash_value = hash('hello')  # 假设值为 12343543
index = hash_value & ( len(indices) - 1)  # 假设index值计算后等于3,具体的hash算法本文不做介绍

# 会找到indices的index为3的位置,并插入enteies的长度
indices = [None, None, None, 0, None, None]
# 此时enteies会插入第一个元素
enteies = [
    [12343543, 'hello', 'word']
]

# 我们继续向字典中添加值
my_dict['haimeimei'] = 'lihua'

hash_value = hash('haimeimei')  # 假设值为 34323545
index = hash_value & ( len(indices) - 1)  # 假设index值计算后同样等于 0

# 会找到indices的index为0的位置,并插入enteies的长度
indices = [1, None, None, 0, None, None]
# 此时enteies会插入第一个元素
enteies = [
    [12343543, 'hello', 'word'],
    [34323545, 'haimeimei', 'lihua']
]

我们在来看一下查询字典的具体过程:

# 下面是一个字典与字典的存储
more_dict = {'name': '张三', 'sex': '男', 'age': 10, 'birth': '2019-01-01'}

# 数据实际存储
indices = [None, 2, None, 0, None, None, 1, None, 3]
enteies = [
    [34353243, 'name', '张三'],
    [34354545, 'sex', '男'],
    [23343199, 'age', 10],
    [00956542, 'birth', '2019-01-01'],
]

print(more_dict['age'])  # 当我们执行这句时

hash_value = hash('age')  # 假设值为 23343199
index = hash_value & ( len(indices) - 1)  # index = 1

entey_index = indices[1]  # 数据在enteies的位置是2
value = enteies[entey_index]  # 所以找到值为 enteies[2]

个人感觉(不一定对):
第一步:利用hash和mask做与运算,获得index
第二步:在indices对应index插入len(enteies),代表enteies的索引
第三步:在enteies插入对应数据

查询即通过 hash后的key与mask 做与运算获得index,从而获得enteies的索引,获取到enteies索引对应的具体数据

由上可以看出,新字典存储数据本身的enteies并不会稀疏,由indices来维护具体存储的位置,enteies中的数据是和插入的数据是一样的,所以新的字典是有序的。

字典的key能使用什么值?
Python字典的key可以使用str, int, tuple等不变数据类型。因为字典是通过hash算法来计算key的值在进行字典操作的,所以key必须为可哈希的,list不能作为字典的key,因为list是可变的。

1.开放寻址法:如果哈希函数计算出来的index位置已经有值,则可以向后探查新的位置来存储这个值。

线性探查:如果位置i被占用,则探查i+1, i+2…
二次探查:如果位置i被占用,则探查i+1^2, i-1^2, i+22,i-22…
二度哈希:有n个哈希函数,当使用第一个hash函数h1发生冲突时,则尝试使用h2,h3…
2.拉链法:hash表每个位置都链接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后
在这里插入图片描述
转载:
https://zhuanlan.zhihu.com/p/74003719
https://www.cnblogs.com/Xuuuuuu/p/13894009.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值