从哈希表到不可变对象,一文搞懂 dict 的核心原理
前言
在 Python 编程中,字典(dict)是我们最常用的数据结构之一。你有没有想过,为什么字典的查找速度那么快?为什么它的 key 必须是不可变类型?今天,我们就来深入探讨这些问题。
一、字典是什么?
简单来说,字典是一种键值对(key-value)存储的数据结构。在 Python 中,我们用花括号 {} 来定义一个字典:
python
# 一个简单的字典示例
student_scores = {
"小明": 95,
"小红": 87,
"小刚": 92
}
如果没有字典,我们怎么做?
假设我们要根据同学的名字查找对应的成绩。没有字典的话,我们可能会用两个列表来模拟:
python
names = ["小明", "小红", "小刚"]
scores = [95, 87, 92]
# 查找"小红"的成绩
index = names.index("小红") # O(n) 遍历查找
score = scores[index] # 87
这种方式的查找时间是 O(n) —— 随着人数增加,查找速度会越来越慢。
而字典的查找时间是 O(1) ,无论数据量多大,速度几乎不变!
二、哈希表:字典背后的秘密
字典之所以这么快,是因为它底层基于哈希表(Hash Table)实现。
哈希表的工作原理
- 存储时:对 key 进行哈希运算,得到一个哈希值 → 转换成数组索引 → 将 value 存入该位置
- 查找时:同样对 key 进行哈希运算 → 直接定位到存储位置 → 取出 value
p3-juejin.byteimg.com/tos-cn-i-k3…
这就好比给每个单词建立了一个拼音索引,你想查一个词,不需要从头翻到尾,直接根据拼音就能找到那一页。
dict vs list:一场时间与空间的博弈
| 特性 | dict | list |
|---|---|---|
| 查找速度 | O(1) 极快 | O(n) 随数据量增加变慢 |
| 内存占用 | 较大 | 较小 |
| 适用场景 | 高速查找 | 顺序存储 |
💡 用空间换时间:字典以消耗更多内存为代价,换来了极快的查找速度。
三、为什么 key 必须是不可变对象?
你有没有遇到过这样的错误?
python
>>> d = {[1, 2, 3]: "value"}
TypeError: unhashable type: 'list'
可哈希(Hashable)是什么?
在 Python 中,一个对象是可哈希的,需要满足:
- 在对象的整个生命周期内,哈希值不变
- 可以与其他对象进行比较
可变对象的问题
python
# 假设 Python 允许这样写
key = [1, 2, 3]
d = {key: "hello"}
# 然后修改了 key
key.append(4) # key 变成了 [1, 2, 3, 4]
如果 key 是可变的,它的哈希值可能会改变。但字典已经根据原来的哈希值把它存到了某个位置,这样就再也找不到这个值了!
这就是为什么字典的 key 必须是不可变类型:
- ✅ 字符串(str)
- ✅ 数字(int, float)
- ✅ 元组(tuple)—— 前提是元组内的元素也是不可变的
- ❌ 列表(list)
- ❌ 字典(dict)
四、Set:没有 value 的字典
Python 的 set(集合)和字典非常相似,它是一组 key 的集合,只是不存储 value。
python
# 创建 set
fruits = {"apple", "banana", "orange"}
# 自动去重
numbers = {1, 2, 2, 3, 3, 3} # 结果是 {1, 2, 3}
# 判断元素是否存在(O(1) 复杂度)
if "apple" in fruits:
print("找到了!")
set 同样要求元素必须是可哈希的,并且元素不能重复。
五、再谈不可变对象
很多人对 Python 的不可变对象有误解。来看一个例子:
python
s = "abc"
print(id(s)) # 假设输出 0x100
s = s.replace("a", "A")
print(s) # "Abc"
print(id(s)) # 输出 0x200(地址变了!)
关键理解:
"abc"这个字符串对象本身永远不会改变replace()方法创建了一个新的字符串"Abc"- 变量
s只是被重新赋值,指向了新的对象
python
# 内存示意图
# "abc" 在 0x100(保持不变)
# "Abc" 在 0x200(新创建)
# s 从指向 0x100 变为指向 0x200
🎯 核心结论:不可变对象一旦创建,其内容永远不能改变。所有看似"修改"的操作,实际上都创建了一个新对象。
六、附录:JavaScript 的小彩蛋
笔记中还提到了一个 JavaScript 的变量提升例子,虽然不是 Python 的内容,但挺有意思的:
javascript
showName(); // 输出什么?
var showName = function() {
console.log(2);
};
function showName() {
console.log(1);
}
// 最终两个调用都输出?猜猜看
答案留给大家思考~(提示:函数声明会优先于变量声明提升)
总结
- 字典基于哈希表实现,查找和插入的时间复杂度为 O(1)
- 字典以空间换时间,速度快但内存消耗大
- key 必须是不可变对象,确保哈希值不变
- set 是只有 key 的字典,同样拥有 O(1) 的查找速度
- 字符串等不可变对象,修改时实际上创建了新对象
如果觉得文章对你有帮助,欢迎点赞、收藏、关注~
有什么问题或想法,欢迎在评论区留言讨论!