python-列表和字典的数据结构

184 阅读2分钟

一、列表

PyListObject 是一个可变对象,支持插入和删除,并且可以动态的调整其做维护的内存大小。

 

"它并不是存了多少东西就申请对应大小的内存,这样的申请策略显然是低效的,因为我们有理由相信,用户选用列表正是为了频繁地插入删除元素 。因此,PyListObject 在每一次需要申请内存的时候,会申请一大块内存,这是申请的总内存的大小记录在allocated 中,而这些内存中实际被有效的 PyObject*占用的内存大小被记录在了 ob_size 中 "

0<=ob_size<=allocated
len(ob_item)=ob_size

ob_item所指向的内存空间是想数组一样的连续内存区域。所以,再第i个位置插入元素,会导致第i个及其后的所有元素都往后移动。

插入关键代码
static int ins1(PyListObject *self, int where, PyObject *v) 
{    int i, n = self->ob_size;    PyObject **items; 
    items = self->ob_item;    for (i = n; --i >= where; )        items[i+1] = items[i]; 
    items[where] = v; 
    return 0;
}

同理,删除元素时,第i+1及其后的元素需要往前移动。

删除关键代码
item = a->ob_item; 
if (d < 0) {
    /* Delete -d items */    
    memmove(&item[ihigh+d], &item[ihigh],    (a->ob_size - ihigh)*sizeof(PyObject *));    
    item = a->ob_item;
} 

二、字典

PyDictObject  使用散列表组织有关联规则的元素对。在最优情况下,散列表能提供 O(1)复杂度的搜索效率 。

(一)entry

散列表的核心思想是:用于搜索的键通过散列函数映射整数,该整数作为索引指向成片的内存区域,用于存储值,如下图所示:

所以,每个存放键值对的关联容器(entry)数据结构如下所示:

typedef struct {    
    long me_hash;     
    PyObject *me_key;    
    PyObject *me_value;
} PyDictEntry; 

随着键值对的插入和删除,entry的状态变化图如下所示:

(二)由entry组成的PyDictObject 对象

一个 PyDictObject 对象实际上是一大堆用散列表组织起来的 entry 的集合 。

ma_used:Active和Dummy状态的entry 的数量

ma_fill:Active状态的entry 的数量

ma_mask:记录了一个 PyDictObject 对象中有 entry 的数量 

ma_lookup:搜索策略函数

无论是插入还是删除,都会先调用 PyDictObject的ma_lookup搜索策略函数查找元素。

插入关键代码(有删减)
static void insertdict(register dictobject *mp, PyObject *key, long hash, PyObject *value)
{
	PyObject *old_value;
	register dictentry *ep;
	ep = mp->ma_lookup(mp, key, hash);
	if (ep->me_value != NULL) {
		old_value = ep->me_value;
                ep->me_value = value;
	} //[1]
        else {
		if (ep->me_key == NULL)   //Unused
			mp->ma_fill++;
		else                      //Dummy
			Py_DECREF(ep->me_key);
		ep->me_key = key;
		ep->me_hash = hash;
		ep->me_value = value;
		mp->ma_used++;
	}  //[2]
}

[1] :搜索成功,返回处于 Active 的 entry,直接替换 me_value。
[2] :搜索失败,返回 Unused 或 Dummy 的 entry,完整设置 me_key, me_hash
和 me_value。  

删除关键代码
int PyDict_DelItem(PyObject *op, PyObject *key){    
    register dictobject *mp;    
    register long hash;    
    register dictentry *ep;    
    PyObject *old_value, *old_key;
    //获得 hash 值    ...    
    //搜索 entry
    mp = (dictobject *)op;
    ep = (mp->ma_lookup)(mp, key, hash);
    //删除 entry 所维护的元素    
    old_key = ep->me_key;    
    Py_INCREF(dummy);    
    ep->me_key = dummy;    
    old_value = ep->me_value;    
    ep->me_value = NULL;   
    mp->ma_used--;    
    Py_DECREF(old_value);    
    Py_DECREF(old_key);    
    return 0;
} 

搜索出要删除的元素,设置entry节点为Dummy态,也即ma_key=dummy,ma_value=NULL。

PyDictObject的搜索策略函数,查看下篇。