问题详情
A
能完全消除类实例的内存开销
B
允许动态添加新属性到实例
C
通过预置固定属性减少__dict__占用
D
会显著提升实例方法的执行速度
回答
- 普通列表/数组:得遍历所有元素(O(n))
- 哈希表:通过一个“魔法函数”(哈希函数),把
"name"→ 变成一个数字(比如12345),然后直接去数组的第12345个位置取值(O(1))
✨ 这就是“哈希”的魔力:把任意 key 映射到固定范围的整数(数组下标)
CPython 的 dict 使用了一种紧凑型哈希表,不是一张二维表,而是两个一维数组:
- 索引数组(indices):表示“哈希值 → entry 位置”的映射
-
- 一个一维数组,每个元素是一个“偏移量”或“entry 编号”
- 初始大小为 8(即使字典为空)
- 类型可能是
int8_t,int16_t,int32_t(根据容量自动选择) - indices = [ -1, -1, -1, 2, -1, -1, -1, 1 ] ↑ ↑ ↑ ↑ ↑ 槽0 … 槽7 (-1是空)
-
查找过程(以
"name"为例):- 计算
hash("name") % len(indices)→ 假设结果是2 - 查看
indices[2]→ 得到2 - 去
entries[2]找 → key 是"name",匹配!返回 value"Alice"
- 计算
- 条目数组(entries):按插入顺序存
(key, value) -
冲突怎么办?——开放寻址(Open Addressing)
如果两个 key 哈希后落到同一个槽(比如
"name"和"game"都映射到槽 2),怎么办?CPython 使用 探测序列(probing):
- 如果槽 2 被占了,就试槽 3、槽 4……直到找到空位或匹配项
- 探测算法是精心设计的(避免聚集)
这叫 开放寻址法(区别于“链地址法”——用链表存冲突项)。
- 空表也占内存,因为要预分配最小容量(通常是 8 个槽):“预留哈希表8个槽位空间(
避免频繁扩容
)” 和 “维护散列表结构”。
用__slots__的对象,没有__dict__→ 不创建哈希表,属性直接存在对象的固定内存偏移处(类似 C struct)
-
dk_indices初始时所有槽位都是-1,表示“空”。 - 当插入一个键(如
"age")时:- 计算
hash("age") - 取模:
index = hash("age") % dk_size(这里dk_size = 8) - 如果
dk_indices[index] == -1,就直接用这个槽 - 否则,按探测序列找下一个空槽(开放寻址)
- 把 entry 的索引号(比如 0)写入该槽
- 计算
测试扩容:
import sys # 测试不同大小 dict 的内存 for n in range(1, 8): d = {i: i for i in range(n)} print(f"n={n}, size={sys.getsizeof(d)}") 输出(典型): Text 编辑 n=1, size=240 n=2, size=240 n=3, size=240 n=4, size=240 n=5, size=240 ← 仍 240! n=6, size=368 ← 扩容了! 根本原因:在“内存浪费”和“频繁扩容”之间找平衡点 哈希表的核心矛盾: 太小 → 插入几个元素就满了 → 频繁 rehash(重建整个表)→ 性能差 太大 → 空表就占很多内存 → 浪费空间 所以必须选一个经验值作为起点。 🔬 二、为什么是 8?而不是 4、16 或 1? 1. 数学依据:负载因子 ≤ 2/3 CPython 要求: 元素数量 ≤ 哈希表容量 × 2/3 所以: 如果初始容量 = 4 → 最多存 floor(4 × 2/3) = 2 个元素 → 插入第 3 个就扩容!太频繁。 如果初始容量 = 8 → 最多存 floor(8 × 2/3) = 5 个元素 → 能容纳大多数“小字典”(如函数局部变量、简单配置等)而不扩容。 如果初始容量 = 16 → 最多存 10 个,但空表内存翻倍(indices 从 32B → 64B+) ✅ 8 是最小的、能容纳“常见小字典”的容量。
为什么说“重”?
| 原因 | 解释 |
|---|---|
| 预留空间 | 空 dict 也分配 8 槽哈希表,为未来插入做准备 |
| 结构复杂 | 需要 indices + entries + 元数据 + 对齐填充 |
| 动态性代价 | 支持任意 key、任意顺序插入、动态扩容 → 必须冗余设计 |
__slots__避免创建__dict__,从而省下这 240 字节/实例
当你需要创建大量结构固定的小对象,且不需要动态属性时,就用
__slots__。
典型行业:
- 游戏开发(实体、粒子)
- 金融/量化(tick 数据、订单)
- 大数据处理(日志、事件流)
- 嵌入式/资源受限环境
-
__dict__是动态字典:支持任意新增属性(如p.email = "x@y.com"),但代价是内存大 -
__slots__是静态结构:属性在类定义时就固定了,不能动态添加,但内存小、访问快
这不是“换个名字”,而是完全不同的底层存储机制:
- 一个是哈希表(dict)
- 一个是连续内存块(类似 C struct)
版权:言论仅代表个人观点,不代表官方立场。转载请注明出处:https://www.stntk.com/question/199.html
还没有评论呢,快来抢沙发~