dict.fromkeys()和OrderedDict.fromkeys()的底层实现原理是什么?

dict.fromkeys()OrderedDict.fromkeys() 的底层实现,核心都是 "利用字典键的唯一性构建映射" ,但因 dictOrderedDict 的底层存储结构不同(前者侧重高效哈希查找,后者侧重严格顺序保留),导致实现细节有差异。以下从「通用逻辑→各自底层实现→差异对比」三方面,结合 Python 源码级原理拆解(以 CPython 为准):

一、先明确:两者的通用实现逻辑

无论 dict 还是 OrderedDict,其 fromkeys() 方法的核心流程完全一致(类方法,无状态依赖),仅底层 "存储键值对 + 记录顺序" 的方式不同:

  1. 接收参数处理

    • 第一个参数 iterable:必须是可迭代对象(如列表、元组),遍历其元素作为字典的 key
    • 第二个参数 value:可选,默认 None,所有 key 共享同一个 value(注意:若 value 是可变对象如列表,所有 key 会引用同一个对象,而非复制)。
  2. 初始化容器 :创建一个空的 dictOrderedDict 实例(底层存储结构不同)。

  3. 遍历迭代对象

    • 按顺序取出 iterable 中的每个元素;
    • 对每个元素,判断是否已作为 key 存在于容器中(通过哈希查找,O (1) 效率);
    • 若不存在:插入容器(key 为当前元素,value 为传入的默认值);
    • 若已存在:跳过(保证 key 唯一性,实现去重)。
  4. 返回容器实例:遍历结束后,返回构建好的字典。

关键共性fromkeys() 是 "类方法"(@classmethod),不依赖实例状态,直接创建新字典;value 是 "共享引用"(而非每个 key 复制一个 value),例如:

python

运行

scss 复制代码
d = dict.fromkeys([1,2,3], [])
d[1].append(4)
print(d)  # {1: [4], 2: [4], 3: [4]}(所有 key 共享同一个列表)

二、dict.fromkeys() 的底层实现(CPython)

Python 中的普通 dict 底层是 "哈希表 + 插入顺序数组" (Python 3.7+ 优化后),兼顾高效查找和顺序保留。fromkeys() 作为 dict 的类方法,底层由 C 语言实现(Objects/dictobject.c),核心步骤如下:

1. 底层数据结构(dict 的核心存储)

普通 dict 内部有三个关键结构(简化模型):

  • 哈希表(Hash Table) :本质是数组(称为 ma_table),每个元素是 dict_entry 结构体(存储 key 指针、value 指针、哈希值 hash、下一个冲突元素指针 next);
  • 插入顺序数组(Insertion Order Array) :数组 ma_order,存储 dict_entry 的指针,按插入顺序排列(Python 3.7+ 新增,用于保留顺序);
  • 哈希冲突解决 :采用 "开放寻址法 + 链表"(早期是链表法,3.11 后优化为开放寻址法,冲突元素通过 next 指针串联)。

2. dict.fromkeys() 的 C 语言实现流程(简化)

c

运行

scss 复制代码
// 伪代码示意(基于 CPython 3.11)
static PyObject * dict_fromkeys(PyTypeObject *type, PyObject *iterable, PyObject *value) {
    // 1. 创建空 dict 实例(分配哈希表和顺序数组内存)
    PyObject *new_dict = PyDict_New();
    if (new_dict == NULL) return NULL;  // 内存分配失败
    
    // 2. 遍历可迭代对象 iterable
    PyObject *it = PyObject_GetIter(iterable);
    if (it == NULL) { Py_DECREF(new_dict); return NULL; }
    
    PyObject *key;
    while ((key = PyIter_Next(it)) != NULL) {
        // 3. 计算 key 的哈希值(需保证 key 是不可变类型,否则报错)
        Py_hash_t hash = PyObject_Hash(key);
        if (hash == -1) {  // 哈希计算失败(如 key 是列表)
            Py_DECREF(key);
            Py_DECREF(it);
            Py_DECREF(new_dict);
            return NULL;
        }
        
        // 4. 查找 key 是否已存在于哈希表中(O(1) 效率)
        PyDictKeyEntry *entry = PyDict_GetEntry(new_dict, key, hash);
        if (entry == NULL) {  // key 不存在,插入新 entry
            // 4.1 向哈希表插入 key(处理冲突)
            if (PyDict_SetItemWithHash(new_dict, key, value, hash) < 0) {
                Py_DECREF(key);
                Py_DECREF(it);
                Py_DECREF(new_dict);
                return NULL;
            }
            // 4.2 向插入顺序数组 ma_order 添加 entry 指针(保留顺序)
            PyDict_AppendOrder(new_dict, entry);
        }
        
        Py_DECREF(key);  // 释放 key 的临时引用
    }
    
    Py_DECREF(it);
    return new_dict;
}

3. 核心细节

  • 哈希计算key 必须是不可变类型(如 int、str、tuple),否则 PyObject_Hash(key) 会报错(这也是字典 key 不可变的底层原因);
  • 查找效率 :通过哈希值定位哈希表索引,冲突时通过 next 指针遍历冲突链表,平均查找效率 O (1);
  • 顺序保留 :Python 3.7+ 后,dict 新增 ma_order 数组,插入新 key 时,将其 dict_entry 指针追加到数组末尾,遍历字典时按数组顺序迭代,实现 "插入顺序保留";
  • 内存分配:哈希表是动态扩容的(负载因子达到 2/3 时,扩容为原大小的 2 倍),避免哈希冲突过多导致效率下降。

三、OrderedDict.fromkeys() 的底层实现(CPython)

OrderedDictcollections 模块的有序字典,底层是 "哈希表 + 双向链表" 结构(Python 3.7+ 后,普通 dict 也支持顺序,但 OrderedDict 保留额外的顺序操作功能)。其 fromkeys() 方法继承自 dict,但底层存储和顺序维护逻辑不同。

1. 底层数据结构(OrderedDict 的核心存储)

OrderedDict 内部包含两个关键部分:

  • 哈希表(同普通 dict) :用于 key 的快速查找(dict_entry 结构体),保证 O (1) 查找效率;
  • 双向链表(Doubly Linked List) :每个 dict_entry 额外包含 prevnext 指针,串联所有 key,严格记录插入顺序(这是与普通 dict 的核心区别)。

2. OrderedDict.fromkeys() 的实现流程(简化)

OrderedDict 没有重写 fromkeys() 方法,直接复用 dict.fromkeys() 的逻辑,但在 key 插入时,会通过 OrderedDict 重写的 __setitem__ 方法,额外维护双向链表:

c

运行

ini 复制代码
// 伪代码示意(基于 CPython 3.11)
// OrderedDict 继承自 dict,复用 fromkeys(),但重写了 PyDict_SetItemWithHash
static int odict_setitem(PyObject *self, PyObject *key, PyObject *value, Py_hash_t hash) {
    // 1. 调用普通 dict 的插入逻辑(哈希表插入 key-value)
    int res = PyDict_SetItemWithHash(self, key, value, hash);
    if (res != 0) return res;
    
    // 2. 额外维护双向链表(OrderedDict 的核心)
    PyDictKeyEntry *entry = PyDict_GetEntry(self, key, hash);
    OrderedDictObject *odict = (OrderedDictObject *)self;
    
    // 2.1 将新 entry 追加到双向链表尾部(保留插入顺序)
    entry->prev = odict->tail;
    entry->next = NULL;
    if (odict->tail != NULL) {
        odict->tail->next = entry;
    } else {
        odict->head = entry;  // 链表为空时,entry 作为头节点
    }
    odict->tail = entry;  // 更新尾节点
    
    return 0;
}

3. 核心细节

  • 复用 dict 基础功能OrderedDictdict 的子类,哈希表的查找、扩容、冲突处理逻辑完全复用普通 dict,仅新增双向链表维护顺序;
  • 双向链表的开销 :每个 dict_entry 多占用 prevnext 两个指针的内存,且插入 / 删除时需额外维护链表指针(时间开销略高于普通 dict);
  • 顺序操作支持 :双向链表使得 OrderedDict 能高效实现 move_to_end()popitem(last=True/False) 等顺序相关操作(普通 dict 不支持)。

四、两者底层实现的核心差异

对比维度 dict.fromkeys()(Python 3.7+) OrderedDict.fromkeys()
底层存储结构 哈希表 + 插入顺序数组(ma_order 哈希表 + 双向链表
顺序维护逻辑 数组追加(插入时记录 entry 指针顺序) 双向链表串联(prev/next 指针)
顺序操作支持 仅保留插入顺序,无额外操作 支持 move_to_end()popitem()
内存开销 较低(数组存储指针,结构简单) 较高(双向链表额外占用 prev/next 指针)
插入 / 删除效率 略高(仅数组追加,无链表操作) 略低(需维护链表指针)
继承关系 内置 dict 的类方法 继承 dict,重写 __setitem__ 维护顺序

五、关键补充:Python 3.7+ 后两者的底层变化

Python 3.7 是一个关键版本,普通 dict 官方承诺 "保留插入顺序",导致 OrderedDict 的定位发生变化:

  1. 普通 dict 的优化 :新增 ma_order 数组记录插入顺序,底层由 "纯哈希表" 变为 "哈希表 + 顺序数组",兼顾效率和顺序;
  2. OrderedDict 的定位 :不再是 "唯一支持顺序的字典",而是 "支持更多顺序操作的字典"(如 move_to_end()、按插入顺序反向迭代等);
  3. 性能差异 :普通 dictfromkeys() 因结构更简单(数组 vs 双向链表),插入和遍历效率略高于 OrderedDict

六、底层实现带来的实际影响

  1. value 共享引用 :两者的 fromkeys() 都让所有 key 共享同一个 value(底层仅存储一个 value 指针),若 value 是可变对象(如列表),修改一个 key 的 value 会影响所有 key;
  2. key 必须不可变 :底层依赖 hash(key) 计算索引,可变对象(列表、字典)无法计算稳定哈希值,会报错;
  3. 去重逻辑:均通过 "哈希表查找 key 是否存在" 实现去重,重复 key 直接跳过,效率 O (1);
  4. 顺序稳定性 :Python 3.7+ 后,两者都能保留插入顺序,但 OrderedDict 的顺序在所有 Python 版本中都稳定(包括 3.7 之前)。

最终总结

dict.fromkeys()OrderedDict.fromkeys() 的底层实现原理可概括为:

  1. 共性:均基于 "哈希表" 实现 key 的快速查找,利用 "key 唯一性" 去重,通过遍历可迭代对象构建字典;
  2. 差异 :普通 dict 用 "插入顺序数组" 维护顺序(高效低耗),OrderedDict 用 "双向链表" 维护顺序(支持更多操作但有额外开销);
  3. 本质fromkeys() 是 "批量构建字典" 的语法糖,底层核心是哈希表的插入逻辑,顺序维护由字典自身的存储结构决定。

理解底层后,就能明白:为什么 Python 3.7+ 日常场景优先用 dict.fromkeys()(高效低耗),而 OrderedDict 仅在需要额外顺序操作时使用。

相关推荐
踏浪无痕31 分钟前
准备手写Simple Raft(四):日志终于能"生效"了
分布式·后端
程序员西西34 分钟前
SpringBoot 隐式参数注入:告别重复代码,让 Controller 更优雅
java·后端
嘻哈baby35 分钟前
Ansible自动化运维:从入门到批量管理100台服务器
后端
Cache技术分享37 分钟前
258. Java 集合 - 深入探究 NavigableMap:新增方法助力高效数据处理
前端·后端
做cv的小昊42 分钟前
在NanoPC-T6开发板上通过USB串口通信实现光源控制功能
java·后端·嵌入式硬件·边缘计算·安卓·信息与通信·开发
用户693717500138443 分钟前
21.Kotlin 接口:接口 (Interface):抽象方法、属性与默认实现
android·后端·kotlin
溪饱鱼43 分钟前
主动与被动AI交互范式
前端·后端·aigc
写代码的皮筏艇44 分钟前
Sequelize 详细指南
前端·后端
用户294655509191 小时前
游戏开发中的向量魔法
后端