Python Dict 和 Set 底层原理:从哈希函数到哈希表全方位解析

引言

在 Python 日常开发中,字典(Dict)和集合(Set)是使用率极高的数据结构

makefile 复制代码
# 字典:键值对存储
user = {
    "name": "Tom",
    "age": 18
}

# 集合:无序元素存储
nums = {1, 2, 3}

我们都熟知它们的核心优势:按键、元素查询的速度极快。

bash 复制代码
user["name"]   # 字典按键取值
1 in nums      # 集合元素判断

但绝大多数开发者只知用法、不懂底层,心中常会有这些疑问:

  • Dict 凭什么查询、取值速度远超列表?
  • Set 为什么可以自动剔除重复元素?
  • 为什么 Dict 的 Key 不能是列表、字典这类可变对象?
  • 哈希(Hash)到底是什么?它和 Dict、Set 有什么关联?

其实 Python Dict 和 Set 的底层逻辑完全同源,二者所有特性都依托于同一个核心结构:哈希表(Hash Table)

完整的底层逻辑链路如下,本文将顺着这条链路层层拆解,帮你建立完整的知识体系:

sql 复制代码
Dict
 ↓
Hash Table(哈希表)
 ↓
Hash Function(哈希函数)
 ↓
Hash Collision(哈希冲突)
 ↓
Mutable / Immutable(可变与不可变对象)
 ↓
Set 的实现原理

一、什么是哈希(Hash)?

哈希的本质是一种不可逆的映射算法 ,可以将任意长度、任意类型的数据,转换为一个固定长度的唯一数字,这个数字就是哈希值(Hash Value)

Python 内置 hash() 函数可以直接计算数据的哈希值:

bash 复制代码
print(hash("Tom"))

输出示例(不同运行环境结果略有差异):

复制代码
876543210

哈希转换的完整流程:

javascript 复制代码
"Tom"
   ↓
Hash Function
   ↓
876543210

我们可以把哈希值理解为数据的唯一身份证号,不同合法数据会对应专属的哈希标识:

yaml 复制代码
Tom    → 1001
Jerry  → 1002
Alice  → 1003

依靠这串专属编号,程序可以直接定位数据,无需逐一比对。

二、为什么需要哈希?核心优势:极致高效

想要理解哈希的价值,最好的对比对象是列表(List)。

假设我们用列表存储一组用户名,判断元素是否存在:

bash 复制代码
users = ["Tom", "Jerry", "Alice"]
print("Tom" in users)

列表的查询逻辑是遍历比对:程序会从第一个元素开始逐一匹配,直到找到目标或遍历完全部元素。

这种查询方式的时间复杂度为 O(n) ,数据量越大,遍历耗时越长,效率越低。

而基于哈希的查询逻辑完全不同:

yaml 复制代码
Tom
 ↓
Hash
 ↓
1001
 ↓
直接定位

全程无需遍历,直接精准寻址,时间复杂度稳定为 O(1)

这就是 Dict、Set 增删查效率碾压列表的核心根本原因

三、什么是哈希表(Hash Table)?底层存储载体

哈希函数只负责「计算编号」,不负责存储数据,真正承载所有数据、实现高效读写的结构是哈希表

哈希表的核心本质可以概括为:数组 + 哈希函数,依托数组的连续存储空间,结合哈希算法实现快速寻址。

我们可以简单将哈希表理解为一个带有序下标、预留空槽位的数组:

diff 复制代码
index
0 
1
2
3
4
5
6
7

数据存入哈希表的流程:

  1. 对数据 Tom 计算哈希值,得到对应数值;
  2. 通过取模运算将哈希值转换为哈希表下标(示例下标为3);
  3. 将数据存入下标为3的槽位中。

存储后结构:

diff 复制代码
index
0
1
2
3 → Tom
4
5
6
7

数据查询时,只需重复「计算哈希值 → 定位下标 → 读取槽位数据」,全程无遍历,效率极高。

四、Dict 字典的底层实现原理

Python 字典是典型的键值对(Key-Value)哈希表,底层完全基于哈希表实现,所有的按键取值、赋值操作,都是依托哈希寻址完成,稳定保持 O(1) 级别的读写效率。

以用户信息字典为例:

makefile 复制代码
user = {
    "name": "Tom",
    "age": 18
}

字典底层的存储单元并非单纯的键值对,而是一组「哈希值+Key+Value」的三元结构,简化示意如下:

css 复制代码
[    (1234, "name", "Tom"),    (5678, "age", 18)]

当我们执行 user["name"] 取值时,底层会严格执行五步逻辑:

步骤1:计算键的哈希值

调用哈希函数,计算 Key 的哈希值:hash("name") = 1234

步骤2:计算存储下标

用哈希值对哈希表总长度取模,得到数据对应的槽位下标:1234 % 表长度 = 目标下标

步骤3:定位存储槽位

根据计算出的下标,直接锁定哈希表中的对应槽位。

步骤4:校验 Key 一致性

对比槽位中存储的 Key 与查询 Key 是否完全一致(用于规避哈希冲突误差)。

步骤5:返回目标 Value

校验通过后,直接取出对应的 Value 值,完成查询。

整个过程无任何遍历操作,这也是字典查询极致高效的核心逻辑。

五、哈希表的核心难题:哈希冲突

哈希算法存在一个无法完全避免的问题:哈希冲突(Hash Collision)

简单来说:不同的数据,经过哈希计算后,得到了相同的哈希下标

举个典型示例:

复制代码
Tom
 ↓
Hash
 ↓
3

Jerry
 ↓
Hash
 ↓
3

两个不同的数据,需要存入同一个槽位,就产生了哈希冲突,若不解决会导致数据覆盖、丢失。

六、Python 解决哈希冲突的方案:开放寻址法

Python 针对 Dict、Set 的哈希冲突,统一采用 开放寻址法(Open Addressing) 解决,核心逻辑是「冲突后顺延找空位」。

具体流程:

  1. 数据 Tom 优先占用下标3的槽位;
  2. 数据 Jerry 计算下标同样为3,触发哈希冲突;
  3. 程序自动向后检索下一个下标4,判断槽位是否为空;
  4. 若下标4为空,则将 Jerry 存入该槽位;若已被占用,继续顺延检索下标5、6......直至找到空槽位。

解决冲突后的最终存储结构:

复制代码
0
1
2
3 → Tom
4 → Jerry
5
6
7

通过这种方式,Python 完美规避了哈希冲突导致的数据异常,保证哈希表数据完整性。

七、核心面试考点:为什么 Dict 的 Key 必须是不可变对象?

这是 Python 高频面试题,核心答案只有一句话:保证哈希值的稳定性

想要彻底弄懂「为什么 Dict 的 Key、Set 的元素必须是不可变对象」,必须先搞懂 Python 中可变对象(Mutable)不可变对象(Immutable) 的核心区别,这是哈希表存储规则的底层前提。

7.1 什么是不可变对象?

不可变对象 :对象创建完成后,内存中的数据内容无法被修改。如果对变量进行赋值、修改操作,不会改动原内存数据,而是会开辟新内存、生成一个全新的对象。

Python 中典型不可变对象:int、float、bool、str、tuple

核心特性:哈希值永久固定

因为内容无法修改,所以其计算出来的哈希值永远不会改变,具备稳定的哈希特性,这也是它能作为 Dict Key、Set 元素的核心原因。

以字符串(不可变对象)为例演示:

bash 复制代码
a = "Tom"
print(hash(a))

# 重新赋值,不是修改原对象,而是生成新对象
a = "Jerry"
print(hash(a))

原字符串 "Tom" 的哈希值始终固定,不会被任何操作修改。

7.2 什么是可变对象?

可变对象 :对象创建后,可以直接修改内存中的原始数据,不会开辟新内存,对象本身始终是同一个。

Python 中典型可变对象:list、dict、set

核心特性:哈希值不稳定、不可哈希

因为内容可以随时被修改,对象的哈希值会跟随内容变化,哈希值不固定,因此不支持哈希运算 ,属于unhashable类型。

以列表(可变对象)为例演示:

bash 复制代码
lst = [1, 2, 3]
# 直接修改原内存数据
lst.append(4)
# 列表内容改变,哈希值会发生变化
print(hash(lst))  # 直接报错

报错原因:可变对象动态可变,Python 不允许对其计算哈希值。

7.3 可变/不可变对象核心区别对照表

特性 不可变对象(Immutable) 可变对象(Mutable)
内存数据 创建后不可修改 可直接原地修改
哈希值 固定、稳定、可哈希 动态变化、不可哈希
能否作为 Dict Key ✅ 可以 ❌ 不可以
能否作为 Set 元素 ✅ 可以 ❌ 不可以
常见类型 int、str、float、bool、tuple list、dict、set

7.4 为什么哈希结构拒绝可变对象?

结合前面的哈希表原理,我们可以彻底闭环这个知识点:

Dict 和 Set 的所有存储、查询、去重逻辑,全部依赖哈希值定位槽位

如果使用可变对象作为 Key / 元素,会出现致命 bug:

  1. 存入数据时:根据「对象旧内容」计算哈希值,存入指定哈希槽位;
  2. 后续修改对象内容:可变对象哈希值随之改变;
  3. 查询数据时:根据「对象新哈希值」寻址,找不到原本存储的数据;
  4. 最终导致:数据存在但查不到,哈希表结构彻底混乱。

这就是 Python 强制规则的底层根源:只有哈希值永久稳定的不可变对象,才能参与哈希表存储

若使用可变对象作为字典 Key,会直接抛出类型错误:

ini 复制代码
# 非法用法
d = {[1, 2]: "hello"}

报错信息:

bash 复制代码
TypeError: unhashable type: 'list'

因此,只有哈希值固定的不可变对象,才能作为 Dict 的 Key、Set 的元素

八、Set 集合的核心特性与去重原理

Set(集合)是 Python 无序、无重复的哈希结构,核心特性:元素唯一、无序存储、查询速度快

最直观的特性就是自动去重:

ini 复制代码
nums = {1, 2, 3, 3, 3}
print(nums)  # 输出:{1, 2, 3}

Set 自动去重的核心原理,依旧依托哈希表

  1. 首次插入元素 1:计算哈希值、定位空槽位,完成存储;
  2. 再次插入重复元素 1:计算出完全一致的哈希值,定位到同一槽位;
  3. 程序校验发现槽位已存在相同元素,直接忽略本次插入,最终实现去重效果。

九、Set 的底层实现:与 Dict 同源复用

很多人误以为 Set 是独立的数据结构,实则不然。Set 和 Dict 底层共用同一套哈希表逻辑,只是存储形式不同

二者核心区别:

  • Dict(字典) :哈希表存储 Key + Value 键值对;
  • Set(集合) :哈希表仅存储 Key,无 Value 概念。

我们可以通俗理解:Set 就是所有 Value 统一为 None 的字典

{1, 2, 3} 底层等价于:

python 复制代码
{1: None, 2: None, 3: None}

精简总结:

  • Dict = 存储 Key+Value 的哈希表
  • Set = 仅存储 Key 的哈希表
ini 复制代码
Dict = HashTable(Key + Value)

Set = HashTable(Key)

十、Dict 与 Set 完整关联关系

二者同源同底层,依托同一套哈希体系实现所有功能,关联关系如下:

哈希函数 → 生成哈希值 → 驱动哈希表运行

哈希表衍生出两大结构:

  • Dict(字典) :存储 Key+Value,支持 O(1) 增删查改,用于键值对数据存储;
  • Set(集合) :仅存储 Key,支持 O(1) 增删查,用于去重、成员判断、集合运算。
scss 复制代码
                Hash Function
                       │
                       ▼
                Hash Table
                 /      \
                /        \
               ▼          ▼

           Dict          Set

        Key+Value       Key

        O(1)查询       O(1)查询
        O(1)插入       O(1)插入
        O(1)删除       O(1)删除

全文总结

Dict 和 Set 的所有特性都不是"语法魔法",全部源于哈希表的底层设计,完整逻辑链路闭环:

sql 复制代码
数据
 ↓
Hash Function
 ↓
Hash Value
 ↓
Hash Table
 ↓
Dict / Set

其中:

  • Hash 负责将数据映射为数字。
  • Hash Table 负责根据哈希值快速存储和查找数据。
  • Dict 与 Set:底层同源,区别仅在于是否存储 Value,Set 本质是无值的字典。
  • 哈希冲突:不同数据哈希下标重合,Python 通过开放寻址法解决
  • 不可变对象 由于哈希值稳定,可以作为 Dict Key 和 Set 元素。
  • 可变对象 哈希值可能变化,因此不能参与哈希表存储。

正因为有这套机制,Python 的 Dict 和 Set 才能实现接近 O(1) 的查询、插入和删除效率。

相关推荐
笨鸟先飞,勤能补拙17 分钟前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
长和信泰光伏储能1 小时前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂2 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study2 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境3 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境3 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1233 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102164 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb004 小时前
一个项目带你入门AI应用开发01
python
0566464 小时前
Python康复训练——常用标准库
开发语言·python·学习