🧠 引言
你知道吗?
- JS 的数组
push()为啥能无限加? - Python 的
list.append()为啥不抛出“超出容量”的错误? - Java 的
ArrayList、C++ 的vector为啥插入效率高?
答案都指向了一个核心机制:动态扩容数组!
本篇文章带你深挖“动态数组”的底层实现机制,并用 JS/Python 双语代码手写一个具备自动扩容能力的动态数组结构!
🧱 一、什么是动态数组?
动态数组是一种能够自动扩容的顺序存储结构,底层依然是“数组 + 容量控制”。
和普通数组区别:
| 特性 | 普通数组 | 动态数组 |
|---|---|---|
| 是否能自动扩容 | ❌ 否 | ✅ 是 |
| 插入末尾效率 | 高 | 高(摊销 O(1)) |
| 内存使用 | 紧凑 | 可能有冗余空间 |
🚀 二、动态扩容核心逻辑
思路:
- 初始化一个固定大小的数组
- 每次插入检查是否超出容量
- 超出则创建一个更大数组(如:原大小的2倍)
- 将旧数据复制进去
关键点:扩容是成倍增长 + 拷贝旧值
💻 三、JS 实现动态数组
class DynamicArray {
constructor(initialSize = 4) {
this.capacity = initialSize;
this.length = 0;
this.data = new Array(this.capacity);
}
push(value) {
if (this.length === this.capacity) {
this._resize();
}
this.data[this.length++] = value;
}
_resize() {
this.capacity *= 2;
const newData = new Array(this.capacity);
for (let i = 0; i < this.length; i++) {
newData[i] = this.data[i];
}
this.data = newData;
}
get(index) {
if (index < 0 || index >= this.length) throw new Error("越界访问");
return this.data[index];
}
print() {
console.log(this.data.slice(0, this.length));
}
}
// 测试
const arr = new DynamicArray();
arr.push(1);
arr.push(2);
arr.push(3);
arr.push(4);
arr.push(5);
arr.print(); // [1, 2, 3, 4, 5]
🐍 四、Python 实现动态数组(模拟 C 风格)
class DynamicArray:
def __init__(self, capacity=4):
self.capacity = capacity
self.length = 0
self.data = [None] * self.capacity
def push(self, value):
if self.length == self.capacity:
self._resize()
self.data[self.length] = value
self.length += 1
def _resize(self):
self.capacity *= 2
new_data = [None] * self.capacity
for i in range(self.length):
new_data[i] = self.data[i]
self.data = new_data
def get(self, index):
if index < 0 or index >= self.length:
raise IndexError("越界访问")
return self.data[index]
def print(self):
print(self.data[:self.length])
# 测试
arr = DynamicArray()
for i in range(5):
arr.push(i + 1)
arr.print() # 输出: [1, 2, 3, 4, 5]
📊 五、扩容策略的差异分析
| 编程语言 | 默认初始容量 | 扩容倍率 | 特点 |
|---|---|---|---|
| Java | 10 | 1.5x | 使用 Arrays.copyOf() 拷贝 |
| C++ | 0 | 2x | vector 使用“翻倍策略” |
| Python | 4 | 动态因子 | 使用 C 扩容机制,策略复杂 |
| JS | 不固定 | 引擎优化 | 取决于 V8 引擎实现 |
⚠️ 六、常见错误与陷阱
| 场景 | 错误理解 | 正确理解 |
|---|---|---|
| 扩容后数据丢失 | 没有拷贝旧数组 | 必须将旧数组元素拷贝进新数组 |
用 slice() 等偷懒方法 | 模拟时需控制边界与容量 | 用原始数组模拟更贴近底层逻辑 |
| 初始容量太小或太大 | 导致频繁扩容或内存浪费 | 建议初始容量设为 4 或 8 |
🎯 七、拓展任务
- 给动态数组加上
insert(index, val)插入功能 - 支持
pop()删除末尾元素 - 设计支持“缩容”的智能数组(如空位太多就减半)
🧩 总结一句话
动态数组是几乎所有现代语言中最常用的数据结构之一,理解其扩容机制,是提升数据结构功力的第一步!
下一篇预告:
📘 第7篇:【从前端视角看图结构】用邻接表和邻接矩阵处理“页面跳转图”!