Python字典底层实现原理详解


Posted in Python onDecember 18, 2019

在Python中,字典是通过散列表或说哈希表实现的。字典也被称为关联数组,还称为哈希数组等。也就是说,字典也是一个数组,但数组的索引是键经过哈希函数处理后得到的散列值。哈希函数的目的是使键均匀地分布在数组中,并且可以在内存中以O(1)的时间复杂度进行寻址,从而实现快速查找和修改。哈希表中哈希函数的设计困难在于将数据均匀分布在哈希表中,从而尽量减少哈希碰撞和冲突。由于不同的键可能具有相同的哈希值,即可能出现冲突,高级的哈希函数能够使冲突数目最小化。Python中并不包含这样高级的哈希函数,几个重要(用于处理字符串和整数)的哈希函数是常见的几个类型。

通常情况下建立哈希表的具体过程如下:

数据添加:把key通过哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。

数据查询:再次使用哈希函数将key转换为对应的数组下标,并定位到数组的位置获取value。

哈希函数就是一个映射,因此哈希函数的设定很灵活,只要使得任何关键字由此所得的哈希函数值都落在表长允许的范围之内即可。本质上看哈希函数不可能做成一个一对一的映射关系,其本质是一个多对一的映射,这也就引出了下面一个概念?哈希冲突或者说哈希碰撞。哈希碰撞是不可避免的,但是一个好的哈希函数的设计需要尽量避免哈希碰撞。

Python2中使用使用开放地址法解决冲突。

CPython使用伪随机探测(pseudo-random probing)的散列表(hash table)作为字典的底层数据结构。由于这个实现细节,只有可哈希的对象才能作为字典的键。字典的三个基本操作(添加元素,获取元素和删除元素)的平均事件复杂度为O(1)。

Python中所有不可变的内置类型都是可哈希的。

可变类型(如列表,字典和集合)就是不可哈希的,因此不能作为字典的键。

常见的哈希碰撞解决方法:

1 开放寻址法(open addressing)

开放寻址法中,所有的元素都存放在散列表里,当产生哈希冲突时,通过一个探测函数计算出下一个候选位置,如果下一个获选位置还是有冲突,那么不断通过探测函数往下找,直到找个一个空槽来存放待插入元素。

开放地址的意思是除了哈希函数得出的地址可用,当出现冲突的时候其他的地址也一样可用,常见的开放地址思想的方法有线性探测再散列,二次探测再散列等,这些方法都是在第一选择被占用的情况下的解决方法。

2 再哈希法

这个方法是按顺序规定多个哈希函数,每次查询的时候按顺序调用哈希函数,调用到第一个为空的时候返回不存在,调用到此键的时候返回其值。

3 链地址法

将所有关键字哈希值相同的记录都存在同一线性链表中,这样不需要占用其他的哈希地址,相同的哈希值在一条链表上,按顺序遍历就可以找到。

4 公共溢出区

其基本思想是:所有关键字和基本表中关键字为相同哈希值的记录,不管他们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。

5 装填因子α

一般情况下,处理冲突方法相同的哈希表,其平均查找长度依赖于哈希表的装填因子。哈希表的装填因子定义为表中填入的记录数和哈希表长度的比值,也就是标志着哈希表的装满程度。直观看来,α越小,发生冲突的可能性就越小,反之越大。一般0.75比较合适,涉及数学推导。

在python中一个key-value是一个entry,

entry有三种状态。

Unused: me_key == me_value == NULL

Unused是entry的初始状态,key和value都为NULL。插入元素时,Unused状态转换成Active状态。这是me_key为NULL的唯一情况。

Active: me_key != NULL and me_key != dummy 且 me_value != NULL

插入元素后,entry就成了Active状态,这是me_value唯一不为NULL的情况,删除元素时Active状态刻转换成Dummy状态。

Dummy: me_key == dummy 且 me_value == NULL

此处的dummy对象实际上一个PyStringObject对象,仅作为指示标志。Dummy状态的元素可以在插入元素的时候将它变成Active状态,但它不可能再变成Unused状态。

为什么entry有Dummy状态呢?这是因为采用开放寻址法中,遇到哈希冲突时会找到下一个合适的位置,例如某元素经过哈希计算应该插入到A处,但是此时A处有元素的,通过探测函数计算得到下一个位置B,仍然有元素,直到找到位置C为止,此时ABC构成了探测链,查找元素时如果hash值相同,那么也是顺着这条探测链不断往后找,当删除探测链中的某个元素时,比如B,如果直接把B从哈希表中移除,即变成Unused状态,那么C就不可能再找到了,因为AC之间出现了断裂的现象,正是如此才出现了第三种状态---Dummy,Dummy是一种类似的伪删除方式,保证探测链的连续性。

提示

一般情况下普通的顺序表数组存储结构也可以认为是简单的哈希表,虽然没有采用哈希函数(取余),但同样可以在O(1)时间内进行查找和修改。但是这种方法存在两个问题:扩展性不强;浪费空间。

set集合和dict一样也是基于散列表的,只是他的表元只包含键的引用,而没有对值的引用,其他的和dict基本上是一致的,所以在此就不再多说了。并且dict要求键必须是能被哈希的不可变对象,因此普通的set无法作为dict的键,必须选择被“冻结”的不可变集合类:frozenset。顾名思义,一旦初始化,集合内数据不可修改。

以上这篇Python字典底层实现原理详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python程序员开发中常犯的10个错误
Jul 07 Python
浅谈python 四种数值类型(int,long,float,complex)
Jun 08 Python
Python机器学习库scikit-learn安装与基本使用教程
Jun 25 Python
PyCharm代码提示忽略大小写设置方法
Oct 28 Python
python并发和异步编程实例
Nov 15 Python
Python设计模式之桥接模式原理与用法实例分析
Jan 10 Python
对Django中static(静态)文件详解以及{% static %}标签的使用方法
Jul 28 Python
Python Django 简单分页的实现代码解析
Aug 21 Python
python 公共方法汇总解析
Sep 16 Python
如何解决django-celery启动后迅速关闭
Oct 16 Python
Python如何利用Har文件进行遍历指定字典替换提交的数据详解
Nov 05 Python
Python django中如何使用restful框架
Jun 23 Python
Python利用PyExecJS库执行JS函数的案例分析
Dec 18 #Python
简单介绍django提供的加密算法
Dec 18 #Python
详解从Django Allauth中进行登录改造小结
Dec 18 #Python
解决pycharm最左侧Tool Buttons显示不全的问题
Dec 17 #Python
python 字段拆分详解
Dec 17 #Python
从pandas一个单元格的字符串中提取字符串方式
Dec 17 #Python
基于pandas中expand的作用详解
Dec 17 #Python
You might like
微信小程序 消息推送php服务器验证实例详解
2017/03/30 PHP
PHP+MySQL实现输入页码跳转到指定页面功能示例
2018/06/01 PHP
js鼠标左右键 键盘值小结
2010/06/11 Javascript
JavaScript 原型学习总结
2010/10/29 Javascript
快速排序 php与javascript的不同之处
2011/02/22 Javascript
随窗体滑动的小插件sticky源码
2013/06/21 Javascript
node.js中的fs.lchown方法使用说明
2014/12/16 Javascript
jQuery 1.9.1源码分析系列(十三)之位置大小操作
2015/12/02 Javascript
原生javascript实现addClass,removeClass,hasClass函数
2016/02/25 Javascript
JavaScript中的继承之类继承
2016/05/01 Javascript
JavaScript实现复制或剪切内容到剪贴板功能的方法
2016/05/23 Javascript
JavaScript 判断一个对象{}是否为空对象的简单方法
2016/10/09 Javascript
Vue 2.5.2下axios + express 本地请求404的解决方法
2018/02/21 Javascript
实例详解Vue项目使用eslint + prettier规范代码风格
2018/08/20 Javascript
js实现简单掷骰子效果
2019/10/24 Javascript
使用vuex较为优雅的实现一个购物车功能的示例代码
2019/12/09 Javascript
JavaScript 链表定义与使用方法示例
2020/04/28 Javascript
Python 序列化 pickle/cPickle模块使用介绍
2014/11/30 Python
python通过线程实现定时器timer的方法
2015/03/16 Python
使用Python脚本操作MongoDB的教程
2015/04/16 Python
Python操作MongoDB数据库PyMongo库使用方法
2015/04/27 Python
python八大排序算法速度实例对比
2017/12/06 Python
Python排序搜索基本算法之选择排序实例分析
2017/12/09 Python
python for 循环获取index索引的方法
2019/02/01 Python
如何用Python做一个微信机器人自动拉群
2019/07/03 Python
利用python实现PSO算法优化二元函数
2019/11/13 Python
荷兰皇家航空公司官方网站:KLM Royal Dutch Airlines
2017/12/07 全球购物
香港家用健身器材、运动器材及健康美容仪器专门店:FitBoxx
2019/12/05 全球购物
What's the difference between Debug and Trace class? (Debug类与Trace类有什么区别)
2013/09/10 面试题
安全标准化汇报材料
2014/02/03 职场文书
小学语文国培感言
2014/03/04 职场文书
2014年流动人口工作总结
2014/11/26 职场文书
九华山导游词
2015/02/03 职场文书
2015年资料员工作总结
2015/04/25 职场文书
python plt.plot bar 如何设置绘图尺寸大小
2021/06/01 Python
Java并发编程必备之Future机制
2021/06/30 Java/Android