python源码剖析笔记_《python源码剖析》笔记 python中的Dict对象

1.PyDictObject对象 -->  C++ STL中的map是基于RB-tree的,搜索时间复杂度是O(logN)

PyDictObject采用了hash表,时间复杂度是O(1)typedef struct{

Py_ssize_t me_hash; //me_key的hash值,避免每次查询都要重新计算一遍hash值

PyObject *me_key;

PyObject *me_value;

}PyDictEntry;

将(key,value)对称为entry,它可以在3种状态间转换:

Unused态 --> me_key和 me_value都为NULL

Active态 --> me_key和 me_value都不为NULL

Dummy态  --> me_key为dummy, me_value为NULL

图5-2

typedef struct _dictobject PyDictObject;

struct _dictobject{

PyObject_HEAD

Py_ssize_t ma_fill; //元素个数: Active + Dummy

Py_ssize_t ma_used; //元素个数: Active

Py_ssize_t ma_mask; //记录了entry的数量

PyDictEntry *ma_table;

PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, long hash);

PyDictEntry ma_smalltable[PyDict_MINSIZE];

}

PyDict_MINSIZE默认设定为8

当 PyDictObject对象的entry数量少于8个, ma_table将指向 ma_smalltable

当 PyDictObject对象的entry数量大于8个, ma_table将指向额外申请的内存空间

Q:这个时候 ma_smalltable中的对象怎么办?

A:ma_smalltable里的对象全都复制到新的table里

图5-3

PyDict_Type --> PyDictObject的类型对象

PyTypeObject PyDict_Type = {

PyVarObject_HEAD_INIT(&PyType_Type, 0)

"dict",

sizeof(PyDictObject),

0,

(destructor)dict_dealloc, /* tp_dealloc */

(printfunc)dict_print, /* tp_print */

0, /* tp_getattr */

0, /* tp_setattr */

(cmpfunc)dict_compare, /* tp_compare */

(reprfunc)dict_repr, /* tp_repr */

0, /* tp_as_number */

&dict_as_sequence, /* tp_as_sequence */

&dict_as_mapping, /* tp_as_mapping */

(hashfunc)PyObject_HashNotImplemented, /* tp_hash */

//...

};

2.PyDictObject的创建

一个途径:

PyDict_New

PyObject* PyDict_New(void)

{

register dictobject *mp;

//[1]:自动创建 dummy对象

//防止探测序列中断

if (dummy == NULL) { /* Auto-initialize dummy */

dummy = PyString_FromString("");

}

if (num_free_dicts)

{

…… //[2]:使用缓冲池

}

else

{

//[3]:创建 PyDictObject对象

mp = PyObject_GC_New(dictobject, &PyDict_Type);

EMPTY_TO_MINSIZE(mp);

}

mp->ma_lookup = lookdict_string;

return (PyObject *)mp;

}

//EMPTY_TO_MINSIZE --> ma_size, ma_fill = 0

//INIT_NONZERO_DICT_SLOT --> 将 ma_table指向 ma_smalltable

元素搜索

static PyDictEntry *

lookdict(PyDictObject *mp, PyObject *key, register long hash)

{

register size_t i;

register size_t perturb;

register PyDictEntry *freeslot; //freeslot用来指向探测序列中第一个处于Dummy态的entry

register size_t mask = (size_t)mp->ma_mask;

PyDictEntry *ep0 = mp->ma_table;

register PyDictEntry *ep;

register int cmp;

PyObject *startkey;

//[1]:hash,定位冲突探测链的第一个entry

i = (size_t)hash & mask; //将哈希值映射到哈希表大小范围内

ep = &ep0[i];

//[2]:

//1. entry处于Unused态

//2. entry中的 key与待搜索的 key匹配

if (ep->me_key == NULL || ep->me_key == key) //**引用相同检查

return ep;

//[3]:第一个entry处于 Dummy态,设置freeslot

if (ep->me_key == dummy)

freeslot = ep;

else {

//检查Active态entry

if (ep->me_hash == hash) {

startkey = ep->me_key;

cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);//**值相同检查

if (cmp < 0)

return NULL;

}

freeslot = NULL;

}

for (perturb = hash; ; perturb >>= PERTURB_SHIFT) {

//[5]:寻找探测链上下一个entry

i = (i << 2) + i + perturb + 1;//?

ep = &ep0[i & mask];

//[6]:到达 Unused态 entry,搜索失败

if (ep->me_key == NULL)

//如果 freeslot不为空,返回freeslot所指的entry

//如果 freeslot为空,返回该 Unused态的 entry

return freeslot == NULL ? ep : freeslot;

if (ep->me_key == key) //“引用相同”规则检查

return ep;

if (ep->me_hash == hash && ep->me_key != dummy) { // "值相同"规则检查

startkey = ep->me_key;

cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);

if (cmp > 0)

return ep;

}

else {

return lookdict(mp, key, hash);

}

}

else if (ep->me_key == dummy && freeslot == NULL)

freeslot = ep;

}

}

//失败或成功返回entry,失败的话,entry的me_value为NULL

插入与删除

insertdict

1.搜索成功,返回处于Active态的 entry,直接替换 me_value

2.搜索失败,返回 Unused态或 Dummy态的entry,完整设置 me_key,me_hash,me_value

在调用insertdict之前会调用PyDict_SetItem,是由 PyDict_SetItem调用 insertdict的

1.计算hash值

2.插入(key, value)元素对 //在这里调用insertdict

3.必要时调整dict的内存空间 //当搜索失败且装载率大于或等于2/3时就调整dict的内存空间

调用 dictresize改变dict的table大小

1.确定新的table的大小

2.如果新的table大小为8,就可以直接使用ma_smalltable

3.否则,需要在堆上申请空间

4.设置新的table

5.处理旧table中的entry:

Active态entry,搬移到新的table中

Dummy态的entry,调整 key的引用计数,丢弃该entry

6.必要时释放旧table所维护的内存空间

PyDict_DelItem

1.获得hash值

2.搜索entry

3.删除 entry所维护的元素,将 entry的状态转为 dummy态

3.PyDictObject对象缓冲池

与PyListObject中使用的缓冲池一样,最初什么都没有,当第一个PyDictObject当销毁时,

这个缓冲池才开始接纳被缓冲的PyDictObject对象

static void

dict_dealloc(register PyDictObject *mp)

{

register PyDictEntry *ep;

Py_ssize_t fill = mp->ma_fill;

//[1]:调整dict中对象的引用计数

for (ep = mp->ma_table; fill > 0; ep++) {

if (ep->me_key) {

--fill;

Py_DECREF(ep->me_key);

Py_XDECREF(ep->me_value);

}

}

//[2]:释放从系统堆中申请的内存空间

if (mp->ma_table != mp->ma_smalltable)

PyMem_DEL(mp->ma_table);

//[3]:将被销毁的PyDictObject对象放入缓冲池

if (numfree < PyDict_MAXFREELIST && Py_TYPE(mp) == &PyDict_Type)

free_list[numfree++] = mp;

else

Py_TYPE(mp)->tp_free((PyObject *)mp);

Py_TRASHCAN_SAFE_END(mp)

}

在创建新的 PyDictObject对象时,如果在缓冲池中有可以使用的对象,则直接从缓冲池中取出使用,而不需要重新创建

原文:http://blog.csdn.net/zhengsenlie/article/details/31015433

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值