首页 > 代码库 > 《python源码剖析》笔记 python中的Dict对象
《python源码剖析》笔记 python中的Dict对象
本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie
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("<dummy key>"); } 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对象时,如果在缓冲池中有可以使用的对象,则直接从缓冲池中取出使用,而不需要重新创建
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。