append() 几乎不搬数据是因为CPython列表预分配冗余空间,多数情况下只需更新计数器;insert() 每次都需平移后续元素,时间复杂度为O(n),头插更达O(n²)。

append() 为什么几乎不搬数据
CPython 的 list 底层是动态数组,但不是“刚好够用”才分配内存,而是预分配冗余槽位。比如当前有 128 个元素,实际分配的内存可能支持到 144 个——这叫 over-allocation。所以多数 append() 只需更新长度计数器和指针,不触发内存复制。
即使扩容发生,也是按比例增长(新容量 ≈ 当前长度 × 1.125 + 小常数),复制全部现有元素一次,均摊下来仍是 O(1)。关键点在于:append() 从不移动已有元素。
insert() 为什么每次都要平移内存
insert(i, x) 不管 i 是 0、5 还是 -2,只要不是末尾(即 i != len(lst)),CPython 就必须从索引 i 开始,把后续所有元素逐个向后拷贝一位。这是纯 C 层的 memmove 操作,无法跳过。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
-
insert(0, x):移动全部n个元素 → 实测比append()慢百倍以上(10 万次时) -
insert(1, x):移动n−1个元素 → 仍为O(n),没有数量级改善 -
insert(len(lst), x):语义等价于append(),但多出索引归一化和范围检查,实测慢 2–3 倍
哪些写法看似合理,实则是性能陷阱
很多人凭直觉写的逻辑,在数据量变大后会突然崩塌,因为时间复杂度不是线性恶化,而是平方级:
立即学习“Python免费学习笔记(深入)”;
- 用
insert(0, x)累积构建倒序列表 → 改用append()后调reverse() - 循环中反复
insert(i, x)维护顺序 → 改用bisect.insort()(它仍调insert(),但至少省了查找开销) - 逐个
insert()插入多个元素 → 收集到临时list,改用切片赋值:lst[i:i] = temp_list,C 层优化过,快一个数量级 - 高频头/尾插入 → 换
collections.deque,两端插入都是O(1)
容易被忽略的底层事实
列表不是链表,没有指针跳转;它是一整块连续内存。这意味着随机访问极快,但任何中间插入都绕不开物理位移。更隐蔽的是:性能崩塌不是“慢一点”,而是当 n 从 1 万涨到 10 万时,总耗时可能暴涨 100 倍以上——因为第 k 次 insert(0, x) 要移动 k−1 个元素,累加就是 O(n²)。


















