文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Python虚拟机集合set实现原理是什么

2023-07-05 14:11

关注

本文小编为大家详细介绍“Python虚拟机集合set实现原理是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Python虚拟机集合set实现原理是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

深入理解 Python 虚拟机:集合(set)的实现原理及源码剖析

数据结构介绍

typedef struct {    PyObject_HEAD    Py_ssize_t fill;                Py_ssize_t used;                    Py_ssize_t mask;        setentry *table;    Py_hash_t hash;                 Py_ssize_t finger;              setentry smalltable[PySet_MINSIZE]; // #define PySet_MINSIZE 8    PyObject *weakreflist;      } PySetObject;typedef struct {    PyObject *key;    Py_hash_t hash;             } setentry;static PyObject _dummy_struct;#define dummy (&_dummy_struct)

上面的数据结果用图示如下图所示:

Python虚拟机集合set实现原理是什么

上面各个字段的含义如下所示:

创建集合对象

首先先了解一下创建一个集合对象的过程,和前面其他的对象是一样的,首先先申请内存空间,然后进行相关的初始化操作。

这个函数有两个参数,使用第一个参数申请内存空间,然后后面一个参数如果不为 NULL 而且是一个可迭代对象的话,就将这里面的对象加入到集合当中。

static PyObject *make_new_set(PyTypeObject *type, PyObject *iterable){    PySetObject *so = NULL;        so = (PySetObject *)type->tp_alloc(type, 0);    if (so == NULL)        return NULL;    // 集合当中目前没有任何对象,因此 fill 和 used 都是 0    so->fill = 0;    so->used = 0;    // 初始化哈希表当中的数组长度为 PySet_MINSIZE 因此 mask = PySet_MINSIZE - 1    so->mask = PySet_MINSIZE - 1;    // 让 table 指向存储 entry 的数组    so->table = so->smalltable;    // 将哈希值设置成 -1 表示还没有进行计算    so->hash = -1;    so->finger = 0;    so->weakreflist = NULL;    // 如果 iterable 不等于 NULL 则需要将它指向的对象当中所有的元素加入到集合当中    if (iterable != NULL) {        // 调用函数 set_update_internal 将对象 iterable 当中的元素加入到集合当中        if (set_update_internal(so, iterable)) {            Py_DECREF(so);            return NULL;        }    }    return (PyObject *)so;}

往集合当中加入数据

首先我们先大致理清楚往集合当中插入数据的流程:

static PyObject *set_add(PySetObject *so, PyObject *key){    if (set_add_key(so, key))        return NULL;    Py_RETURN_NONE;}static intset_add_key(PySetObject *so, PyObject *key){    setentry entry;    Py_hash_t hash;    // 这里就查看一下是否是字符串,如果是字符串直接拿到哈希值    if (!PyUnicode_CheckExact(key) ||        (hash = ((PyASCIIObject *) key)->hash) == -1) {      // 如果不是字符串则需要调用对象自己的哈希函数求得对应的哈希值        hash = PyObject_Hash(key);        if (hash == -1)            return -1;    }    // 创建一个 entry 对象将这个对象加入到哈希表当中    entry.key = key;    entry.hash = hash;    return set_add_entry(so, &entry);}static intset_add_entry(PySetObject *so, setentry *entry){    Py_ssize_t n_used;    PyObject *key = entry->key;    Py_hash_t hash = entry->hash;    assert(so->fill <= so->mask);      n_used = so->used;    Py_INCREF(key);    // 调用函数 set_insert_key 将对象插入到数组当中    if (set_insert_key(so, key, hash)) {        Py_DECREF(key);        return -1;    }    // 这里就是哈希表的核心的扩容机制    if (!(so->used > n_used && so->fill*3 >= (so->mask+1)*2))        return 0;    // 这是扩容大小的逻辑    return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4);}static intset_insert_key(PySetObject *so, PyObject *key, Py_hash_t hash){    setentry *entry;    // set_lookkey 这个函数便是插入的核心的逻辑的实现对应的实现函数在下方    entry = set_lookkey(so, key, hash);    if (entry == NULL)        return -1;    if (entry->key == NULL) {                entry->key = key;        entry->hash = hash;        so->fill++;        so->used++;    } else if (entry->key == dummy) {                entry->key = key;        entry->hash = hash;        so->used++;    } else {                Py_DECREF(key);    }    return 0;}// 下面的代码就是在执行我们在前面所谈到的逻辑,直到找到相同的 key 或者空位置才退出 while 循环static setentry *set_lookkey(PySetObject *so, PyObject *key, Py_hash_t hash){    setentry *table = so->table;    setentry *freeslot = NULL;    setentry *entry;    size_t perturb = hash;    size_t mask = so->mask;    size_t i = (size_t)hash & mask;     size_t j;    int cmp;    entry = &table[i];    if (entry->key == NULL)        return entry;    while (1) {        if (entry->hash == hash) {            PyObject *startkey = entry->key;                        assert(startkey != dummy);            if (startkey == key)                return entry;            if (PyUnicode_CheckExact(startkey)                && PyUnicode_CheckExact(key)                && unicode_eq(startkey, key))                return entry;            Py_INCREF(startkey);            // returning -1 for error, 0 for false, 1 for true            cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);            Py_DECREF(startkey);            if (cmp < 0)                                                          return NULL;            if (table != so->table || entry->key != startkey)                     return set_lookkey(so, key, hash);            if (cmp > 0)                                                          return entry;            mask = so->mask;                         }        if (entry->hash == -1 && freeslot == NULL)            freeslot = entry;        if (i + LINEAR_PROBES <= mask) {            for (j = 0 ; j < LINEAR_PROBES ; j++) {                entry++;                if (entry->key == NULL)                    goto found_null;                if (entry->hash == hash) {                    PyObject *startkey = entry->key;                    assert(startkey != dummy);                    if (startkey == key)                        return entry;                    if (PyUnicode_CheckExact(startkey)                        && PyUnicode_CheckExact(key)                        && unicode_eq(startkey, key))                        return entry;                    Py_INCREF(startkey);                    // returning -1 for error, 0 for false, 1 for true                    cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);                    Py_DECREF(startkey);                    if (cmp < 0)                        return NULL;                    if (table != so->table || entry->key != startkey)                        return set_lookkey(so, key, hash);                    if (cmp > 0)                        return entry;                    mask = so->mask;                }                if (entry->hash == -1 && freeslot == NULL)                    freeslot = entry;            }        }        perturb >>= PERTURB_SHIFT; // #define PERTURB_SHIFT 5        i = (i * 5 + 1 + perturb) & mask;        entry = &table[i];        if (entry->key == NULL)            goto found_null;    }  found_null:    return freeslot == NULL ? entry : freeslot;}

哈希表数组扩容

在 cpython 当中对于给哈希表数组扩容的操作,很多情况下都是用下面这行代码,从下面的代码来看对应扩容后数组的大小并不简单,当你的哈希表当中的元素个数大于 50000 时,新数组的大小是原数组的两倍,而如果你哈希表当中的元素个数小于等于 50000,那么久扩大为原来长度的四倍,这个主要是怕后面如果继续扩大四倍的话,可能会浪费很多内存空间。

set_table_resize(so, so-&gt;used&gt;50000 ? so-&gt;used*2 : so-&gt;used*4);

首先需要了解一下扩容机制,当哈希表需要扩容的时候,主要有以下两个步骤:

这里需要注意的是因为数组的长度发生了变化,但是 key 的哈希值却没有发生变化,因此在新的数组当中数据对应的下标位置也会发生变化,因此需重新将所有的对象重新进行一次插入操作,下面的整个操作相对来说比较简单,这里不再进行说明了。

static intset_table_resize(PySetObject *so, Py_ssize_t minused){    Py_ssize_t newsize;    setentry *oldtable, *newtable, *entry;    Py_ssize_t oldfill = so->fill;    Py_ssize_t oldused = so->used;    int is_oldtable_malloced;    setentry small_copy[PySet_MINSIZE];    assert(minused >= 0);            for (newsize = PySet_MINSIZE;         newsize <= minused && newsize > 0;         newsize <<= 1)        ;    if (newsize <= 0) {        PyErr_NoMemory();        return -1;    }        oldtable = so->table;    assert(oldtable != NULL);    is_oldtable_malloced = oldtable != so->smalltable;    if (newsize == PySet_MINSIZE) {                newtable = so->smalltable;        if (newtable == oldtable) {            if (so->fill == so->used) {                                return 0;            }                        assert(so->fill > so->used);            memcpy(small_copy, oldtable, sizeof(small_copy));            oldtable = small_copy;        }    }    else {        newtable = PyMem_NEW(setentry, newsize);        if (newtable == NULL) {            PyErr_NoMemory();            return -1;        }    }        assert(newtable != oldtable);    memset(newtable, 0, sizeof(setentry) * newsize);    so->fill = 0;    so->used = 0;    so->mask = newsize - 1;    so->table = newtable;        if (oldfill == oldused) {        for (entry = oldtable; oldused > 0; entry++) {            if (entry->key != NULL) {                oldused--;                set_insert_clean(so, entry->key, entry->hash);            }        }    } else {        for (entry = oldtable; oldused > 0; entry++) {            if (entry->key != NULL && entry->key != dummy) {                oldused--;                set_insert_clean(so, entry->key, entry->hash);            }        }    }    if (is_oldtable_malloced)        PyMem_DEL(oldtable);    return 0;}static voidset_insert_clean(PySetObject *so, PyObject *key, Py_hash_t hash){    setentry *table = so->table;    setentry *entry;    size_t perturb = hash;    size_t mask = (size_t)so->mask;    size_t i = (size_t)hash & mask;    size_t j;    // #define LINEAR_PROBES 9    while (1) {        entry = &table[i];        if (entry->key == NULL)            goto found_null;        if (i + LINEAR_PROBES <= mask) {            for (j = 0; j < LINEAR_PROBES; j++) {                entry++;                if (entry->key == NULL)                    goto found_null;            }        }        perturb >>= PERTURB_SHIFT;        i = (i * 5 + 1 + perturb) & mask;    }  found_null:    entry->key = key;    entry->hash = hash;    so->fill++;    so->used++;}

从集合当中删除元素 pop

从集合当中删除元素的代码如下所示:

static PyObject *set_pop(PySetObject *so){        Py_ssize_t i = so->finger & so->mask;    setentry *entry;    PyObject *key;    assert (PyAnySet_Check(so));    if (so->used == 0) {        PyErr_SetString(PyExc_KeyError, "pop from an empty set");        return NULL;    }    while ((entry = &so->table[i])->key == NULL || entry->key==dummy) {        i++;        if (i > so->mask)            i = 0;    }    key = entry->key;    entry->key = dummy;    entry->hash = -1;    so->used--;    so->finger = i + 1;             return key;}

上面的代码相对来说也比较清晰,从 finger 开始寻找存在的元素,并且删除他。我们在前面提到过,当一个元素被删除之后他会被赋值成 dummy 而且哈希值为 -1 。

读到这里,这篇“Python虚拟机集合set实现原理是什么”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯