本文为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

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里





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对象时,假设在缓冲池中有能够使用的对象,则直接从缓冲池中取出使用,而不须要又一次创建

《python源代码剖析》笔记 python中的Dict对象的更多相关文章

  1. Python源代码剖析笔记3-Python运行原理初探

    Python源代码剖析笔记3-Python执行原理初探 本文简书地址:http://www.jianshu.com/p/03af86845c95 之前写了几篇源代码剖析笔记,然而慢慢觉得没有从一个宏观 ...

  2. 《python源代码剖析》笔记 python中的List对象

    本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.PyListObject对象 --> 变长可变对象,可看作vector<Py ...

  3. 《python源代码剖析》笔记 Python的编译结果

    本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.python的运行过程 1)对python源码进行编译.产生字节码 2)将编译结果交给p ...

  4. 《python源代码剖析》笔记 Python虚拟机框架

    本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1. Python虚拟机会从编译得到的PyCodeObject对象中依次读入每一条字节码指令 ...

  5. 《python源代码剖析》笔记 python环境初始化

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/zhsenl/article/details/33747209 本文为senlie原创.转载请保留此地 ...

  6. 《python解释器源码剖析》第6章--python中的dict对象

    6.0 序 元素和元素之间可能存在着某种关系,比如学生姓名和成绩.我希望能够通过学生的姓名找到这个学生的成绩,那么只需要将两者关联起来即可.字典正是这么做的,字典中的每个元素就是一个key:value ...

  7. Python 源码剖析(五)【DICT对象】

    五.DICT对象 1.散列表概述 2.PyDictObject 3.PyDictObject的创建与维护 4.PyDictObject 对象缓冲池 5.Hack PyDictObject 这篇篇幅较长 ...

  8. Redis源代码剖析和凝视(八)--- 对象系统(redisObject)

    Redis 对象系统 1. 介绍 redis中基于双端链表.简单动态字符串(sds).字典.跳跃表.整数集合.压缩列表.高速列表等等数据结构实现了一个对象系统,而且实现了5种不同的对象,每种对象都使用 ...

  9. 《python源代码剖析》笔记 python虚拟机中的函数机制

    本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.Python虚拟机在运行函数调用时会动态地创建新的 PyFrameObject对象, 这 ...

随机推荐

  1. jsp页面中定时的方法

    $(function(){ totaladd(); //定时时触发的函数 setInterval(totaladd,3000);//设置定时1000=1秒 }); function totaladd( ...

  2. jQuery事件与动画

    一 事件 1 加载DOM事件 $(document).ready():执行时机:DOM元素准备就绪  执行次数:多次  简单写法:原:$(document).ready(function(){})  ...

  3. Asp服务器控件(HyperLink、Button) 绑定后台参数 DataBinder.Eval

    HyperLink动态绑定参数 <asp:HyperLink id="MbCenterHLnk" runat="server" Text='会员中心' T ...

  4. Windows下配置环境变量和需不需要重启问题

    http://blog.163.com/guomaolin_gavin/blog/static/19961830720121114929321/

  5. Nagios配置—添加linux主机监控

    nagios安装请参看:Nginx平台安装Nagios监控服务 下面是我添加linux监控机的过程,如有错误或者不当的地方请指出: 测试环境: 监控主机:nagios+nagios插件+nrpe+网站 ...

  6. MyEclipse导入Maven项目报错 Plugin execution not covered by lifecycle configuration:

    web项目使用到mybatis,需要使用mybatis的自动生成代码插件,配置build部分如下: <build> <pluginManagement></pluginM ...

  7. UVa12304

    基础题,注意精度和旋转方向. #include <iostream> #include <math.h> #include <vector> #include &l ...

  8. Javascript 中神奇的 this

    Javascript 当中的 this 与其他语言是完全不同的机制,很有可能会让一些编写其他语言的工程师迷惑. 1. 误以为 this 指向函数自身 根据 this 的英语语法,很容易将函数中出现的  ...

  9. CentOS下安装vsftpd架设ftp服务器

    什么是vsftpd vsftpd是一款在Linux发行版中最受推崇的FTP服务器程序.特点是小巧轻快,安全易用. 首先安装vsftpd这个软件,命令是,yum install vsftpd servi ...

  10. 猜数字-js

    var n = Math.round(Math.random()*10); //随机数 // alert(n); while(true){ var Onum = prompt('请输入1-10之间的数 ...