我们在上一篇文章中分析了 CPython 中是如何实现 dict 的。由于涉及内容较多,我可能并没有描述清楚 dict 所用的哈希表的具体结构。 如果你也对此有疑问,不妨来看一下本文对哈希表结构的进一步分析。 【dict 哈希表】 先看一张重新制作的图。 这张图描述的就是 CPython 中 PyDictKeysObject 结构中的成员变量 dk_indices。它的数据类型是 char[],表明它是一块连续分配的内存。 dk_indices 在使用中被分割为两部分。 第一部分是图中的 Slots 数组,也就是我们之前文章中提到的哈希表索引内存块;第二部分是 KeyValues 数组,我们之前称之为哈希表键值对内存块。这两部分都是连续的内存,都可以当做数组来处理,从而能充分利用数组随机访问的特性,提高 dict 的访问效率。 Slots 数组有两个作用:
图中的 slot 有 3 种取值:-1、-2 和自然数。分别代表着 slot 的三种状态:
哈希表的 hash 算法和冲突算法都是运行在这个 Slots 数组上的。 计算 slot 的基本方法为: slot = hash(key) & (dk_size - 1) 当发生 key 冲突时,会采用如下算法在 Slots 中查找下一个合适的 slot: const size_t mask = DK_MASK(keys); // 计算初始 slot size_t i = hash & mask; //获取 slot 中的索引值 Py_ssize_t ix = dictkeys_get_index(keys, i); //循环检查索引值是否有效 for (size_t perturb = hash; ix >= 0;) { perturb >>= PERTURB_SHIFT; i = (i*5 + perturb + 1) & mask; ix = dictkeys_get_index(keys, i); } KeyValues 数组用来存储键值对。键值对是以追加的方式添加到数组的尾部的,添加完成之后,将其位置索引保存在 key 对应的 slot 中。 dict.items()、dict.keys() 和 dict.values() 这三个方法就是直接访问 KeyValues 数组的,因而元素的输出顺序和插入顺序是一致的。 【使用这种结构的好处】 首先,使用连续的内存可充分利用数组随机访问的特性,保证了 dict 的访问效率。 其次,Slots 数组只保存元素的索引,每个 slot 只占用少量固定长度的内存,可大大节省内存空间。 我们可以想象,当 dict 比较大时,如果其中只包含少量有效的(Active) slot,此时的 Slots 就是一个稀疏的数组。如果 Slots 中存储的是元素对象,每个元素对象的 size 要远大于一个 int 索引值占用的空间,这个稀疏数组会造成比较严重的内存浪费。 因此,CPython 所采用的这种哈希表内存结构的确是一种高效紧凑的数据存储方式,具有很高的时间和空间效率。 【相关文章】 我埋头写作,就像在努力爬坡 你在看和分享,无形中推了我一把 多希望你可以一直关注着我 那是我不懈前行的力量 |
|
来自: RealPython > 《python 技术》