探秘 Python 字典:为什么它的查找速度如此之快?

0 阅读4分钟

从哈希表到不可变对象,一文搞懂 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)实现。

哈希表的工作原理

  1. 存储时:对 key 进行哈希运算,得到一个哈希值 → 转换成数组索引 → 将 value 存入该位置
  2. 查找时:同样对 key 进行哈希运算 → 直接定位到存储位置 → 取出 value

p3-juejin.byteimg.com/tos-cn-i-k3…

这就好比给每个单词建立了一个拼音索引,你想查一个词,不需要从头翻到尾,直接根据拼音就能找到那一页。

dict vs list:一场时间与空间的博弈

特性dictlist
查找速度O(1) 极快O(n) 随数据量增加变慢
内存占用较大较小
适用场景高速查找顺序存储

💡 用空间换时间:字典以消耗更多内存为代价,换来了极快的查找速度。

三、为什么 key 必须是不可变对象?

你有没有遇到过这样的错误?

python

>>> d = {[1, 2, 3]: "value"}
TypeError: unhashable type: 'list'

可哈希(Hashable)是什么?

在 Python 中,一个对象是可哈希的,需要满足:

  1. 在对象的整个生命周期内,哈希值不变
  2. 可以与其他对象进行比较

可变对象的问题

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);
}

// 最终两个调用都输出?猜猜看

答案留给大家思考~(提示:函数声明会优先于变量声明提升)


总结

  1. 字典基于哈希表实现,查找和插入的时间复杂度为 O(1)
  2. 字典以空间换时间,速度快但内存消耗大
  3. key 必须是不可变对象,确保哈希值不变
  4. set 是只有 key 的字典,同样拥有 O(1) 的查找速度
  5. 字符串等不可变对象,修改时实际上创建了新对象

如果觉得文章对你有帮助,欢迎点赞、收藏、关注~

有什么问题或想法,欢迎在评论区留言讨论!