动态数组核心是连续内存+智能扩容+封装管理,底层用连续内存块保障O(1)随机访问,扩容策略依场景选1.5倍(Java)、2倍(部分C++)或渐进式(Python),封装size/capacity/指针并提供append/get/reserve等安全接口,避免拷贝低效、溢出、迭代器失效及零容量陷阱。

核心是用“连续内存 + 智能扩容策略 + 封装管理”三者结合,不是简单每次加1,也不是无脑翻倍,而是在时间开销和空间浪费之间找平衡点。
底层必须用连续内存块
动态数组的随机访问快(O(1)),靠的就是底层一块连续空间。不能用链表拼接,也不能分散分配小块内存。C++用new T[n]、Java用new Object[n]、Python CPython底层用PyMem_Malloc,都是为了保证地址连续。访问arr[i]能直接算出地址,这是性能根基。
扩容策略决定整体效率
常见策略有三种,选哪种要看场景:
-
1.5倍增长(Java ArrayList 默认):位运算
old + (old >> 1)高效计算,内存利用率高,扩容次数适中。例如容量从10→15→22→33→49……适合通用场景。 - 2倍增长(部分C++ vector 实现):逻辑最简,摊还时间复杂度更稳定,但大容量时浪费明显。比如从1024→2048,空出1024个位置可能永远用不上。
-
渐进式增长(Python list):公式近似
old + old//8 + offset,小数组增幅大(如1→4→8),大数组增幅缓(1024→1136),兼顾响应速度与内存温和增长。
封装关键状态与操作接口
一个可用的动态数组类至少要维护三个字段:数据指针(或引用)、当前元素数(size)、已分配容量(capacity)。对外只暴露安全接口:
-
append / add:先检查
size == capacity,触发扩容再存值; -
get / set:只校验
index < size,不查capacity,避免冗余判断; -
reserve:允许用户预分配,跳过多次小步扩容(比如知道要塞1000个元素,直接
reserve(1000)); - 不自动缩容:pop后保留空间,防止反复伸缩抖动——这是所有主流实现的共识。
避免常见性能陷阱
写实现时容易忽略但影响很大:
- 拷贝元素必须用
std::move(C++)或System.arraycopy(Java)等高效方式,避免逐个调用拷贝构造; - 扩容时新容量不能溢出,尤其在大数组场景下要检查
MAX_ARRAY_SIZE边界; - 迭代器/指针在扩容后全部失效,若暴露底层指针,必须文档注明“扩容后无效”;
- 首次分配别从0开始——Java空构造用
EMPTY_ELEMENTDATA,C++ vector首插后设为1,Python首次append后设为1,都是为避免零容量分支判断。



















