【Python】第 4 章:Python 数据结构实现

第 4 章:Python 数据结构实现

4.1 list (动态数组)

原理讲解

Python 的 list 是过度分配的动态数组

复制代码
┌─────────────────────────────────────────────────────────┐
│              list 内存布局                              │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  PyListObject 结构:                                    │
│  ┌──────────────────────────────────────────┐          │
│  │ ob_refcnt  (引用计数)                     │          │
│  │ ob_type    (类型指针)                     │          │
│  │ ob_size    (元素数量)                     │          │
│  │ allocated  (分配的容量)                   │          │
│  │ *items     (指向元素数组的指针)           │          │
│  └──────────────────────────────────────────┘          │
│                        ↓                                │
│  ┌──────────────────────────────────────────┐          │
│  │ [item0][item1][item2][item3][  ][  ]...  │          │
│  │  ↑                    ↑                  │          │
│  │  ob_size=2           allocated=8         │          │
│  └──────────────────────────────────────────┘          │
│                                                         │
│  过度分配策略:                                         │
│  new_allocated = (new_size >> 3) + (3 if new_size < 9 else 6)  │
│                                                         │
└─────────────────────────────────────────────────────────┘

过度分配策略:

python 复制代码
# CPython 源码中的增长策略
# 新容量 = 当前大小 + (当前大小 >> 3) + 常数
# 大约是 1.125 倍增长

# 实际容量增长序列:
# 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...

append 的摊销复杂度:

复制代码
┌─────────────────────────────────────────────────────────┐
│              append 操作分析                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  情况 1: 有剩余空间                                      │
│  ┌─────────────────────────────────┐                   │
│  │ [A][B][C][D][ ][ ][ ][ ]        │                   │
│  │              ↑                  │                   │
│  │            append(E)            │                   │
│  │ 操作:直接放入,O(1)             │                   │
│  └─────────────────────────────────┘                   │
│                                                         │
│  情况 2: 需要扩容                                        │
│  ┌─────────────────────────────────┐                   │
│  │ [A][B][C][D][E][F][G][H]        │ 满了!            │
│  │              ↓                  │                   │
│  │ 1. 分配新数组 (1.125 倍)          │                   │
│  │ 2. 复制所有元素                  │                   │
│  │ 3. 添加新元素                    │                   │
│  │ 操作:O(n), 但摊销后 O(1)         │                   │
│  └─────────────────────────────────┘                   │
│                                                         │
└─────────────────────────────────────────────────────────┘

list 的内存布局

c 复制代码
// CPython 源码 (Include/cpython/listobject.h)
typedef struct {
    PyObject_VAR_HEAD      // ob_refcnt, ob_type, ob_size
    PyObject **ob_item;    // 指向元素数组
    Py_ssize_t allocated;  // 分配的容量
} PyListObject;

查看 list 内部信息:

python 复制代码
import sys

lst = [1, 2, 3]
print(f"list 对象大小:{sys.getsizeof(lst)}")
print(f"元素数量:{len(lst)}")

# 计算容量(通过添加元素测试)
test_list = []
for i in range(1000):
    size_before = sys.getsizeof(test_list)
    test_list.append(i)
    size_after = sys.getsizeof(test_list)
    if size_after != size_before:
        print(f"添加第 {i} 个元素时扩容:{size_before} → {size_after}")

4.2 dict (哈希表)

哈希表实现

Python 3.6+ 的紧凑 dict:

复制代码
┌─────────────────────────────────────────────────────────┐
│           Python 3.6+ dict 结构                         │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  传统 dict (3.5 及之前):                                 │
│  ┌─────────────────────────────────────────┐           │
│  │ [hash1, key1, value1]                   │           │
│  │ [hash2, key2, value2]                   │           │
│  │ [hash3, key3, value3]                   │           │
│  │ ... (稀疏,浪费空间)                     │           │
│  └─────────────────────────────────────────┘           │
│                                                         │
│  紧凑 dict (3.6+):                                      │
│  ┌─────────────────────────────────────────┐           │
│  │  indices (索引数组)                     │           │
│  │  [2, 0, 1, -1, -1, ...]                 │           │
│  └─────────────────────────────────────────┘           │
│                        ↓                                │
│  ┌─────────────────────────────────────────┐           │
│  │  entries (紧凑数组)                     │           │
│  │  ┌─────────────────────────────────┐   │           │
│  │  │ [hash0, key0, value0]           │   │           │
│  │  │ [hash1, key1, value1]           │   │           │
│  │  │ [hash2, key2, value2]           │   │           │
│  │  └─────────────────────────────────┘   │           │
│  └─────────────────────────────────────────┘           │
│                                                         │
│  优势:                                                 │
│  - 节省 20-25% 内存                                     │
│  - 保持插入顺序                                         │
│  - 迭代更快                                             │
│                                                         │
└─────────────────────────────────────────────────────────┘

哈希冲突解决:开放寻址法

python 复制代码
# 伪代码展示查找过程
def dict_lookup(key):
    hash_value = hash(key)
    index = hash_value & mask  # mask = size - 1
    
    while True:
        entry = table[index]
        
        if entry is EMPTY:
            return NOT_FOUND
        
        if entry.key == key:
            return entry.value
        
        # 冲突!使用扰动函数找下一个位置
        index = perturb(index, hash_value)

dict 结构:

c 复制代码
// CPython 源码 (Include/cpython/dictobject.h)
typedef struct {
    PyObject_HEAD
    Py_ssize_t ma_used;      // 使用中的条目数
    Py_ssize_t ma_version;   // 版本号(用于迭代检查)
    PyDictKey *ma_keys;      // 键数组
    PyObject **ma_values;    // 值数组
} PyDictObject;

哈希冲突解决

复制代码
┌─────────────────────────────────────────────────────────┐
│              哈希冲突解决示例                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  假设:hash("apple") % 8 = 3                            │
│       hash("orange") % 8 = 3  (冲突!)                   │
│                                                         │
│  使用扰动函数找新位置:                                  │
│  perturb = perturb >> 5 | perturb * 0x9e3779b9         │
│                                                         │
│  索引表:                                               │
│  ┌───┬───┬───┬───┬───┬───┬───┬───┐                    │
│  │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │                    │
│  ├───┼───┼───┼───┼───┼───┼───┼───┤                    │
│  │   │   │   │ 0 │   │   │ 1 │   │                    │
│  │   │   │   │↑  │   │   │↑  │   │                    │
│  │   │   │   ││  │   │   ││  │   │                    │
│  │   │   │   │apple  │   │orange │                    │
│  └───┴───┴───┴───┴───┴───┴───┴───┘                    │
│                                                         │
│  "apple" 在位置 3                                       │
│  "orange" 冲突,扰动后放到位置 6                          │
│                                                         │
└─────────────────────────────────────────────────────────┘

4.3 set 和 tuple

set 的哈希表实现

set 与 dict 的关系:

python 复制代码
# set 本质上是只有 key 的 dict
my_set = {1, 2, 3}
# 内部实现类似:
# {1: None, 2: None, 3: None}

set 的特性:

  • 无序(Python 3.7+ 保持插入顺序用于迭代,但不保证)
  • 元素必须可哈希
  • 成员测试 O(1)
python 复制代码
# set 的内存效率
import sys

lst = [1, 2, 3, 4, 5]
st = {1, 2, 3, 4, 5}

print(f"list 大小:{sys.getsizeof(lst)}")
print(f"set 大小:{sys.getsizeof(st)}")
# set 通常比 list 大,因为有哈希表开销

tuple 的不可变性

tuple 结构:

c 复制代码
// CPython 源码
typedef struct {
    PyObject_VAR_HEAD
    PyObject *ob_item[1];  // 变长数组
} PyTupleObject;

不可变性的优势:

复制代码
┌─────────────────────────────────────────────────────────┐
│              tuple vs list                              │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  tuple (不可变):                                        │
│  - 内存紧凑(不需要额外空间用于增长)                   │
│  - 可哈希(可用作 dict 的 key)                         │
│  - 创建更快                                             │
│  - 线程安全(不需要锁)                                 │
│                                                         │
│  list (可变):                                           │
│  - 需要过度分配                                         │
│  - 不可哈希                                             │
│  - 支持增删改                                           │
│  - 更灵活                                               │
│                                                         │
│  性能对比:                                             │
│  - tuple 创建比 list 快约 30%                           │
│  - tuple 迭代略快                                       │
│  - tuple 内存占用更小                                   │
│                                                         │
└─────────────────────────────────────────────────────────┘

4.4 实践:对比不同数据结构的性能

实验代码

python 复制代码
# examples/chapter-04/data_structure_performance.py
import sys
import timeit
import random

print("=" * 70)
print("Python 数据结构性能对比实验")
print("=" * 70)

# ========== 1. 内存对比 ==========
print("\n【实验 1】内存占用对比")
print("-" * 50)

sizes = [100, 1000, 10000]

for n in sizes:
    data = list(range(n))
    
    lst = list(data)
    tpl = tuple(data)
    st = set(data)
    dct = {i: i for i in data}
    
    print(f"\nn = {n}:")
    print(f"  list:  {sys.getsizeof(lst):>8} bytes")
    print(f"  tuple: {sys.getsizeof(tpl):>8} bytes")
    print(f"  set:   {sys.getsizeof(st):>8} bytes")
    print(f"  dict:  {sys.getsizeof(dct):>8} bytes")

# ========== 2. 查找性能 ==========
print("\n【实验 2】查找性能对比 (10000 次查找)")
print("-" * 50)

n = 10000
data = list(range(n))
search_targets = [random.randint(0, n-1) for _ in range(10000)]

lst = data
st = set(data)
dct = {i: i for i in data}

# list 查找
time_list = timeit.timeit(
    lambda: [x for x in search_targets if x in lst],
    number=1
)

# set 查找
time_set = timeit.timeit(
    lambda: [x for x in search_targets if x in st],
    number=1
)

# dict 查找
time_dict = timeit.timeit(
    lambda: [dct[x] for x in search_targets if x in dct],
    number=1
)

print(f"  list:  {time_list*1000:>8.2f} ms  (O(n) 查找)")
print(f"  set:   {time_set*1000:>8.2f} ms  (O(1) 查找)")
print(f"  dict:  {time_dict*1000:>8.2f} ms  (O(1) 查找)")
print(f"  加速比:set 比 list 快 {time_list/time_set:.0f}x")

# ========== 3. append 性能 ==========
print("\n【实验 3】append 性能对比 (追加 10000 个元素)")
print("-" * 50)

time_list_append = timeit.timeit(
    lambda: [lst := []] + [lst.append(i) for i in range(10000)],
    number=10
) / 10

print(f"  list.append: {time_list_append*1000:>8.2f} ms")
print(f"  平均每次 append: {time_list_append*1000/10000:.4f} ms")

# ========== 4. 创建性能 ==========
print("\n【实验 4】创建性能对比")
print("-" * 50)

time_list_create = timeit.timeit(
    lambda: list(range(10000)),
    number=100
)

time_tuple_create = timeit.timeit(
    lambda: tuple(range(10000)),
    number=100
)

time_set_create = timeit.timeit(
    lambda: set(range(10000)),
    number=100
)

time_dict_create = timeit.timeit(
    lambda: {i: i for i in range(10000)},
    number=100
)

print(f"  list:  {time_list_create*10:>8.2f} ms")
print(f"  tuple: {time_tuple_create*10:>8.2f} ms  (快 {time_list_create/time_tuple_create:.2f}x)")
print(f"  set:   {time_set_create*10:>8.2f} ms")
print(f"  dict:  {time_dict_create*10:>8.2f} ms")

# ========== 5. 迭代性能 ==========
print("\n【实验 5】迭代性能对比 (遍历 10000 个元素)")
print("-" * 50)

n = 10000
lst = list(range(n))
tpl = tuple(range(n))
st = set(range(n))

time_list_iter = timeit.timeit(
    lambda: sum(lst),
    number=100
)

time_tuple_iter = timeit.timeit(
    lambda: sum(tpl),
    number=100
)

time_set_iter = timeit.timeit(
    lambda: sum(st),
    number=100
)

print(f"  list:  {time_list_iter*10:>8.2f} ms")
print(f"  tuple: {time_tuple_iter*10:>8.2f} ms")
print(f"  set:   {time_set_iter*10:>8.2f} ms")

# ========== 6. dict 扩容测试 ==========
print("\n【实验 6】dict 扩容观察")
print("-" * 50)

d = {}
prev_size = sys.getsizeof(d)

for i in range(1000):
    d[i] = i
    curr_size = sys.getsizeof(d)
    if curr_size != prev_size:
        print(f"  插入第 {i} 个元素时扩容:{prev_size} → {curr_size} bytes")
        prev_size = curr_size

实验练习

练习 1:观察 list 扩容行为

python 复制代码
import sys

def observe_list_growth():
    """观察 list 的过度分配"""
    lst = []
    prev_size = sys.getsizeof(lst)
    prev_capacity = 0
    
    for i in range(100):
        lst.append(i)
        curr_size = sys.getsizeof(lst)
        
        if curr_size != prev_size:
            # 估算容量(每个指针 8 字节)
            curr_capacity = (curr_size - 56) // 8
            print(f"长度={i+1:>3} | 大小={curr_size:>4} | "
                  f"容量≈{curr_capacity:>3} | 利用率={100*(i+1)/curr_capacity:.1f}%")
            prev_size = curr_size

observe_list_growth()

练习 2:测试哈希冲突对性能的影响

python 复制代码
import timeit

# 创建一个有哈希冲突的场景
class BadHash:
    """所有对象哈希值相同"""
    def __init__(self, val):
        self.val = val
    def __hash__(self):
        return 42  # 故意返回相同值
    def __eq__(self, other):
        return isinstance(other, BadHash) and self.val == other.val

# 创建 dict
bad_dict = {BadHash(i): i for i in range(1000)}
normal_dict = {i: i for i in range(1000)}

# 测试查找性能
bad_time = timeit.timeit(
    lambda: sum(bad_dict.get(BadHash(500), 0)),
    number=1000
)

normal_time = timeit.timeit(
    lambda: sum(normal_dict.get(500, 0)),
    number=1000
)

print(f"哈希冲突 dict: {bad_time*1000:.2f} ms")
print(f"正常 dict: {normal_time*1000:.2f} ms")
print(f"性能下降:{bad_time/normal_time:.1f}x")

练习 3:比较不同大小 dict 的内存效率

python 复制代码
import sys

def dict_memory_efficiency():
    """测试 dict 的内存效率"""
    sizes = [10, 100, 1000, 10000]
    
    print("dict 内存效率分析:")
    print("大小\tdict 大小\t每条目\t负载因子")
    print("-" * 50)
    
    for n in sizes:
        d = {i: i for i in range(n)}
        size = sys.getsizeof(d)
        per_item = size / n
        
        # 估算容量(简化)
        # 实际需要使用更复杂的方法
        print(f"{n}\t{size}\t{per_item:.1f}\t-")

dict_memory_efficiency()

常见问题

Q1: 为什么 list 查询是 O(n) 而 dict 是 O(1)?

A:

  • list 是数组,需要线性搜索
  • dict 是哈希表,通过哈希函数直接计算位置
python 复制代码
# list: 需要遍历
5 in [1, 2, 3, 4, 5]  # 最坏检查所有元素

# dict: 直接计算位置
5 in {1, 2, 3, 4, 5}  # 计算 hash(5),直接访问

Q2: 为什么 tuple 比 list 快?

A:

  • tuple 不需要过度分配
  • tuple 不需要检查可变性
  • tuple 内存布局更紧凑
  • tuple 可以被优化(如常量折叠)

Q3: dict 保持顺序是从哪个版本开始的?

A:

  • Python 3.6: CPython 实现细节
  • Python 3.7: 语言规范保证

Q4: 如何选择使用 list 还是 tuple?

A:

python 复制代码
# 使用 tuple 当:
- 数据不应该改变
- 用作 dict 的 key
- 需要哈希
- 性能敏感

# 使用 list 当:
- 需要增删元素
- 需要排序
- 需要切片赋值

Q5: set 和 dict 的哈希表实现有什么区别?

A:

  • set 只存储 key(类似 dict 的 keys)
  • dict 存储 key-value 对
  • 底层哈希算法相同
  • set 的 value 是固定的(存在性)

本章小结

  • list 是过度分配的动态数组,append 摊销 O(1)
  • dict 在 3.6+ 使用紧凑布局,节省内存并保持顺序
  • 哈希表使用开放寻址法解决冲突
  • set 是只有 key 的哈希表
  • tuple 是不可变数组,更紧凑更高效
  • 选择合适的数据结构对性能至关重要

下一章预告

第 5 章将深入探讨 字符串与编码,包括 Unicode、UTF-8 和字符串驻留机制。

相关推荐
和小潘一起学AI2 小时前
CentOS 7安装Anaconda
开发语言·python
kcuwu.2 小时前
Python 正则表达式从入门到实战
数据库·python·正则表达式
不解不惑3 小时前
langchain qwen3 构建一个简单的对话系统
pytorch·python·langchain
努力努力再努力dyx3 小时前
【无标题】
开发语言·python
I疯子3 小时前
2026-04-07 打卡第 4 天
python
数据知道3 小时前
claw-code 源码分析:Tool Pool 组装——默认策略、过滤、MCP 开关如何影响「可用工具面」?
python·claude code·claw code
samroom3 小时前
【鸿蒙应用开发 Dev ECO Studio 5.0版本】从0到1!从无到有!最全!计算器------按钮动画、滑动退格、中缀表达式转后缀表达式、UI设计
数据结构·ui·华为·typescript·harmonyos·鸿蒙
Zzj_tju3 小时前
Java 从入门到精通(十二):File 与 IO 流基础,为什么程序“读写文件”时总是容易出问题?
java·python·php
汽车搬砖家3 小时前
vSOMEIP系列 -6: vsomeip python版部署,双机跨域通信(vsomeip - davinci AP someip)
python·汽车