当前位置: 首页 > news >正文

python源码分析:dict对象的实现

源代码选用 最常见的 cpython

首先来看看构建dict的基础设施:

typedef struct {
Py_ssize_t me_hash;
PyObject *me_key;
PyObject *me_value;
} PyDictEntry;

这个结构体为dict中key-value,其中的me_hash为me_key的hash值,[空间换时间]。除此之外,我们发现me_key与me_value都是PyObject指针类型,这也说明了为什么dict中的key与value可以为python中的任何类型数据。

struct _dictobject {
PyObject_HEAD
Py_ssize_t ma_fill;
Py_ssize_t ma_used;
Py_ssize_t ma_mask;
PyDictEntry *ma_table;
PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, long hash);
PyDictEntry ma_smalltable[PyDict_MINSIZE];
};

这个结构体便是dict了。按照我们通常的理解,dict应该是可变长对象啊!为什么这里还有PyObject_HEAD,而不是PyObject_VAR_HEAD。仔细一看,dict的可变长与 string,list,tuple 仍有不同之外,后者可以通过PyObject_VAR_HEAD中的ob_size来指明其内部有效元素的个数。但dict不能这样做,所以dict干脆绕开PyObject_VAR_HEAD,而且除了有ma_used这个字段来交代出其有效元素的个数,还需要ma_fill来交代清楚曾经有效元素的个数(用来计算加载率)。

ma_mask,则牵扯到hash中的散列函数;
ma_smalltable,python一向的有限空间换时间,一个小池子来应付大多数的小dict(不超过PyDict_MINSIZE);
ma_lookup,则是一次探测与二次探测函数的实现。

在展开dict实现细节前,先把dict使用的解决冲突的开放定址法介绍一下。我们知道哈希,就是将一个无限集合映射到一个有限集,如果选择理想的hash函数,能够将预期处理到的元素均匀分布到有限集中即可在O(1)时间内完成元素查找。但理想的hash函数是不存在的,且由于映射的本质(无限到有限)必然出出现一个位置有多个元素要‘占据’,这就需要解决冲突。现有的解决冲突的方法:

  1. 开放定址法
  2. 链地址法
  3. 多哈希函数法
  4. 建域法

其中建域法基本思想为假设哈希函数的值域为[0,m-1],则设向量HashTable[0..m-1]为基本表,另外设立存储空间向量OverTable[0..v]用以存储发生冲突的记录。

其中前两种方法实现最为简单高效,下面回顾下开放定址与链地址法。

开放定址法:形成hash表时,某元素在第一次探测其应该占有的位置时,如果发现此处(记为A)已经被别人占了,那就在从A开始,再次探测(当然这次探测使用的hash函数与第一次已经不一样了),如果发现还是被别人占了,那么继续探测,至到找到一个可用位置(也有可能在当下条件下永远找不到)。开放地址法有一个至关重要的问题需要解决,那就是在一个元素离开hash表时,如何处理离开后的位置状态。如果设置为原始空状态,那么后续的有效元素就无法识别了,因为在查找时同样是依据上面的探测规则进行查找,所以必须告诉探测函数某个位置虽然无有效元素了,但后续的探测可能会出现有效元素。我们可以发现,开放定址法很容易发生冲突(主要是一次探测以上成功的元素占取其它元素应该在第一次探测成功的位置),所以就需要加大hash有效空间。

链地址法:链地址法的思想很简单,你不是可能会出现多个元素对应同一个位置,那么我就在这个位置拉出一个链表来存放所以hash到这个位置的元素。很简单吧,还节约内存呢!很遗憾,python的设计者没有选它。

那为什么python发明者选择了开放定址而不是链地址法,在看python源码时看到这么一段话:

Open addressing is preferred over chaining since the link overhead(开销) for chaining would be substantial(大量) (100% with typical malloc overhead).

由于链地址法需要动态的生成链表结点(malloc),所以时间效率不如开放定址法(但开放定址法的装载率不能高于2/3,相对于链地址法的空间开销也是毋庸置疑的),由此可以看出python的设计时代已经不是那个内存只有512k可供使用的时代了,对内存的苛刻已经让步于效率。当然这需要考虑到python由于实现动态而必须靠自身的设计将损失的时间效率尽可能地补回来。

好了,交待完开放定址法与为什么python设计者选择它后,我们来看看dict如何实现这个算法的。前面已经看到每个key-value由一个Entry结构体实现,python就是利用entry自身的信息来指明每个位置的状态:原始空状态、有效元素离去状态、有效元素占据状态。

  • 原始空:me_key:Null ;me_value:Null
  • 有效元素离去:me_key:dummy; me_value:Null
  • 有效元素占据:me_key:not Null and not dummy ;me_value:not Null

其中dict的hash方法与冲突解决方法的思路如下:

lookdict(k,v)

  1. index <- hash1(k),freeslot<-Null,根据me_key与me_value选择2、3、4一个执行;
  2. 查看index处的值处于’有效元素占据‘状态,判断data[index]与v是否一致(地址或内容),一致,则返回查找成功;否则转5
  3. index所指向的位置处于’原始空‘状态,查找失败,若freeslot==Null返回index;否则返回freeslot;转5
  4. index所指向的位置处于’有效元素离去‘状态,freeslot<-index, 转5
  5. index <- hash2(index),,转2

dict的lookdict方法实现充分体现了python对内存的利用率与空间换时间提高效率上,表现为如下方面:

    1. 内存利用率:当找到原始空状态时,如果前面已经找到dummy态的entry,则会将其返回。
    2. 提高效率:ma_table始终指向有效散列空间的开始位置,在开辟新空间后,small_table就弃之不用了,ma_table改指向新开辟空间的首位置。

 

转载于:https://www.cnblogs.com/muyiblog/p/7662262.html

相关文章:

  • 轻院1089 阶乘的最高位
  • Spring MVC遭遇checkbox的问题解决方式
  • Mac转Windows的拯救指南
  • thinkphp 3.1.3 redis 只能读取 无法写入的问题
  • VMWare平台应用细节
  • Docker容器虚拟化(一)—安装与镜像管理
  • Servlet常用方法
  • Mysql性能优化一
  • Android Animation(动画)---基础一
  • java:练习学校学生
  • [译]从形式到功能,设计思维的改变
  • Azure 基础:Queue Storage
  • 机器学习算法 Python R速查表
  • Nginx+Keepalived主备
  • Spotify模式并非“敏捷涅磐”
  • 3.7、@ResponseBody 和 @RestController
  • Apache Pulsar 2.1 重磅发布
  • bootstrap创建登录注册页面
  • chrome扩展demo1-小时钟
  • input实现文字超出省略号功能
  • iOS编译提示和导航提示
  • JavaScript 是如何工作的:WebRTC 和对等网络的机制!
  • Java教程_软件开发基础
  • jquery ajax学习笔记
  • Koa2 之文件上传下载
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • pdf文件如何在线转换为jpg图片
  • Rancher-k8s加速安装文档
  • Spark学习笔记之相关记录
  • vue-loader 源码解析系列之 selector
  • vue自定义指令实现v-tap插件
  • 不发不行!Netty集成文字图片聊天室外加TCP/IP软硬件通信
  • 给Prometheus造假数据的方法
  • 后端_MYSQL
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 面试遇到的一些题
  • 前嗅ForeSpider采集配置界面介绍
  • 使用 5W1H 写出高可读的 Git Commit Message
  • 原生Ajax
  • 自动记录MySQL慢查询快照脚本
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • 如何用纯 CSS 创作一个货车 loader
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • # Apache SeaTunnel 究竟是什么?
  • #Linux(权限管理)
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • $.ajax()
  • (09)Hive——CTE 公共表达式
  • (10)STL算法之搜索(二) 二分查找
  • (附源码)php新闻发布平台 毕业设计 141646
  • (附源码)spring boot校园健康监测管理系统 毕业设计 151047
  • (十五)Flask覆写wsgi_app函数实现自定义中间件
  • (五)网络优化与超参数选择--九五小庞