为了刻画某种关系,现代的编程语言都会提供关联式的容器。关联式容器中的元素分别是以(键(key)或值(value))这样的形式存在。例如(3,5)(3,6)就是一对对应的键与值。

Python中的关联式容器是PyDictObject。Python通过PyDictObject建立执行Python字节码的运行环境,其中会存放变量名和变量值的元素对,通过查找变量名获得变量值。

PyDictObject运用了(hash table)散列表,最优情况下能达到O(1)复杂度的搜索效率。

散列表:

python 离散数据拟合成正态分布 离散元 python_索引

基本思想:键值映射整数,通过一片连续内存的索引对应相对的值

散列效率高,但是不同的对象运用散列函数有可能会产生相同的散列值、即是易冲突

相关概念:装载率(如果散列表一共可以容纳10个元素,而当前已经装入6个元素,那么装载率就是6/10),研究表明当散列表的装载率大于2/3时,散列冲突的发生概率大大增加

解决办法:开放定址法(通过一个二次探测函数f,计算下一个地址,如果可用则插入不可用则计算下一个候选位置)

缺点:删除时不能完成删除元素,否则会造成断链,解决的办法是:伪删除


entry/slot ->关联容器(键)

一个entry的定义如下:

typedef struct {  
Py_ssize_t me_hash;  //记录me_key 的散列值,避免每次都要计算
PyObject *me_key;   //指向键
PyObject *me_value; //指向值
} PyDictEntry;




因为key和value 都是PyObject,故什么东西都可以放进去Dict


entry有三种状态:


①Unused态 →key,value = Null


②Active 态 → key!= dummy,key!=Null,value != Null


③Dummy 态 → key = Dummy, value = Null


三态的转换关系如下:




python 离散数据拟合成正态分布 离散元 python_python_02






在PyDictObject定义中,



python 离散数据拟合成正态分布 离散元 python_索引_03

有一个ma_smalltable(PyDict_MINSIZE) 意味着至少有X个entry被创建(在这里X=8, 可在源码修改)


定义是分两种情况:①元素数目小于8, ma_table 指向 ma_smalltable 


②元素数目大于8, ma_table指向额外内存



第一次调用PyNew的时候会创建Dummy对象,Dummy则指向一个PyStringObject对象



元素的搜索:lookdict、lookdict_string-(算法相同)  后者是为键值为String提供便利 因为在python中一PyStringObject对象作为PyDictObject的键是十分常见的,故lookdict_string成为了PyDictObject中默认的搜索算法



dict的key匹配值有两层含义:


①引用相同,即两符号指向同一地址


②值相同,两个对象指向不同的地址,但是值相同


d = {}
d[9527] = 'Python'
print(d[9527]) 
//上面两个9527指向不同的地址,但值相同



接下来看看第一次搜索:


[1]根据hash获得entry的索引,这里是第一个索引


[2]if entry处于Unused态,即字典中无这个key,第一个索引失败 if entry->me_key ==  key,表明匹配,搜索成功


[3]if entry 处于Dummy态,设置freeslot(最后如果找不到是会返回freeslot,提示系统这里有一个dummy,快来用掉它)


[4]if entry 处于active ,检查是否值相同,若相同则搜索成功



所第一个entry不匹配,则沿着探测链,顺藤摸瓜,依次比较探测链上的entry与带查找的key


①若搜索成功,则ep一定指向一个有效的entry


②若搜索不成功,此时ep肯定处于一个Unused态的entry


不能直接返回,因为有可能在搜索链的过程中发现dummy,故须返回给Python使用



lookdict_string(有条件限制)(优化)


key是PyDictObject


if(!PyString_checkEcact(key)){
    mp->ma_lookup = lookdict;
    return lookdict(mp_key, hash);
}


lookdict_string 效率比lookdict高



元素插入:


ep = mp->ma_lookup(mp, key, hash);


成功:


if (ep->me_value != Null){
old_value = ep->me_value;
ep->me_value = new_value;
Py_DECREF(old_value);
Py_DECREF(key);
}

else将key, value覆给ep(ep可以为Dummy,Unused)



在插入元素的动作结束后,会检查是否需要改变PyDictObject内部ma_table的内存大小


条件:当增加元素个数且装载率大于2/3时



改变内存大小由dictresize执行


[1]确定新的table大小,这个大小要大于传入的数值,从8开始乘以2的指数倍(2的n此方),直到超过传入的数值


[2]==8,不需要重新分配内存,直接指向ma_smalltable


[3]>=8,重新分配内存


[5]对非Unused做处理,若为active则插入,若为dummy则丢弃,也就是重新做字典,类似一个新字典里面是没有dummy的


[6]如果旧的table指向内存(>8)则释放



删除:与插入很像,先计算hash值,找到相对应的entry,删除维护的元素,将active转为dummy,调整table使用情况(ma_used--1)



字典结束