• 【python-dict】dict的使用及实现原理


    以下内容是针对:python源码剖析中的第五章——python中Dict对象 的读书笔记(针对书中讲到的内容进行了自己的整理,并且针对部分内容根据自己的需求进行了扩展)

     

    一、Dict的用法

    Dict的对象在使用到了所谓的关联关系的时候,就是通过key-value的形式,能够通过key值快速定位到某个value值;

     

    Dict的相关操作如下:

    class mydict(object):

        def __init__(self):

            self.d = {}

       

        def fuzhi(self):

            self.d = {'2':23, '3':'34'}

       

        def fuzhi2(self):

            self.d[1] = 1

            self.d['str'] = 123

            self.d['may'] = 234

            self.d['may'] = 789

           

        def delkey(self):             

            del(self.d[1])         #删除key时,key值必须存在于dict中,否则会报出 KeyError

            self.d.pop('str')

           

        def bianli(self):

            for (k,v) in self.d.items():

                print k, v

       

        def getkeysandv(self):

            print self.d.keys()              #获取当前dict中的所有key值

     

            #获取某个元素值:

            print self.d.get('345')         #若key不存在,则返回默认返回值None

            print self.d.get('123', -1)   #若key不存在,则返回自定义的返回值-1

            print self.d.get('may')    #若key值存在,则返回对应的value

           

        def setkeyexcept(self):

            self.d[[1,2,3]] =123       #可变对象不能作为key,会提示:TypeError: unhashable type: 'list' 的类似错误

     

    二、Dict的存储实现原理

    python中的dict对象也即PyDictObject对象,因为对搜索的效率要求很高,所以选择了散列表(hash table),因为在最优情况下,散列表能够提供O(1)的搜索效率

    因此:这里就能想到在leetcode上面刷的题目中,很多通过list形式可以实现的,为了降低时间复杂度,可以用hash的方式,选择dict对象存储(当然具体问题要具体分析)

    散列表的基本思想是:通过一定的函数将需要搜索的键值映射为一个整数,根据这个整数作为索引去访问某片连续的内存区域。用于映射的函数称为映射函数,映射所产生的值称为散列值(hash value)。散列函数对搜索效率有直接的决定性作用。在使用散列函数将不同的值可能映射到相同的散列值,这个时候就需要冲突解决(装载率大于2/3时,冲突的概率就会大大增加)

    冲突解决在python中使用的是开放定址法,就是通过一个二次探测函数f,计算下一个位置,一直到找到下一个可用的位置为止,在这个过程中会到达多个位置,这些位置就形成了一个“冲突探测链”,这个冲突探测链在查找某个元素的时候起到重要作用,所以在删除某个位置上的元素,不能直接将这个位置的内容删除,如果删除的话,则导致后续依赖于这个位置的其他值就都无法寻找到了,所以只能进行“伪删除”(通过给元素设置状态,dummy态,表示没有存储具体的值但是还会用到的废弃态)

     

     

    具体的PyDictObject对象中,会存在每一个元素,元素的定义是:

    typedef struct{

        Py_ssize_t me_hash;

        PyObject *me_key;

        PyObject *me_value;

        }PyDictEntry;

    每一个元素有三种状态,分别是:unused、active、dummy

    状态

    具体含义

    unused

    me_key=Null,me_value=Null

    active

    me_key!=Null,me_value!=Null

    dummy

    me_key=dummy,me_value=Null

     

    PyDictObject对象的定义:

    #define PyDict_MINSIZE 8

    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;

        PyDictEntry *ma_table;

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

        PyDictEntry ma_smalltable[PyDict_MINSIZE];

        }

    其中各个字段的含义如下:ma_fill域中维护着从PyDictObject对象创建开始直到现在为止所有曾经及正在处于active态的entry个数;ma_used域中维护着当前正处于Active态的entry个数;ma_smalltable的PyDictEntry的数组,表示的是当创建一个PyDictObject对象的时候,至少创建PyDict_MINISIZE个entry被创建(这个数量在程序中定义是8个,这个数量是经过长期的经验所得来的值);ma_table是指向一块区域的,如果entry的数量不超过8个,那么这个指针就指向了ma_smalltable的地址,如果超过了8个,就需要申请一块大的内存,并且ma_table指向这块大的内存

     

    三、Dict的操作实现原理(包括插入、删除、以及缓冲池等)

    首先介绍:PyDictObject对象的元素搜索策略:

    有两种搜索策略,分别是lookdict和lookdict_string,lookdict_string就是lookdict在对于PyStringObject进行搜索时的特殊形式,那么通用的搜索策略lookdict的主要逻辑是:

    (1)对第一个entry的查找:

    a)根据hash值获得entry的索引

    b)若entry处于unused态,则搜索结束;若entry所指向的key与搜索的key相同,则搜索成功

    c)若当前entry处于dummy态,则设置freeslot(这里的freeslot是可以返回作为下一个立即可用的地址来存储entry)

    d)检查Active态的entry,若其key所指向的值与搜索的值相同,则搜索成功

     

    (2)对剩余的探测链中的元素的遍历查找:

    a)根据所采用的探测函数,获得探测链上的下一个待检查的entry

    b)检查到一个unused态的entry,表明搜索失败:

    如果freeslot不为空,则返回freeslot;否则返回unused态的entry

    c)检查entry的key与所搜索的key的引用是否相同,相同则搜索成功,返回entry

    d)检查entry的key与所搜索的key的值是否相同,相同则搜索成功,返回entry

    e)遍历过程中,发现dummy态的entry,且freeslot未设置,则设置freeslot

     

    接下来是:PyDictObject对象的元素插入与删除的策略:

    需要首先用到搜索策略,搜索成功,则直接将值进行替换,搜索失败,返回unused态或dummy态的entry,设置key、value和hash值,并且根据目前插入的元素情况进行ma_table的大小的调整(调整的依据就是装载率,根据是否大于2/3来进行调整);删除也是类似,先计算hash值,然后搜索相应的entry,搜索成功,删除entry中维护的元素,将entry从Active态修改为dummy态

     

    在PyDictObject的实现过程中,会用到缓冲池,在PyDictObject对象被销毁的时候,才开始接纳被缓冲的PyDictObject对象,定义的缓冲池可接纳的对象数量是80个,创建新PyDictObject对象的时候,如果缓冲池中有,则可以直接从缓冲池中取出使用

  • 相关阅读:
    [PATCH] input: add driver for Bosch Sensortec's BMA150 accelerometer
    linux内核GPIO模拟I2C实例
    修改默认apn数据的方法
    Android平台开发WIFI function portingWIFI功能移植
    Silverlight中后台获取样式的方法
    Silverlight中读取Word
    Silverlight找到模板中的子控件
    根据年月来获取该年该月的天数
    Silverlight中Treeview中判断当前节点是否含有父节点
    修改 Linux /etc/profile 以后如何生效
  • 原文地址:https://www.cnblogs.com/keke-xiaoxiami/p/8329779.html
Copyright © 2020-2023  润新知