"如果说编程是一场冒险,那么数组就是你的第一个背包!" 🎒
😄 什么是数组?一个生活化的比喻
想象一下,你去超市买了一盒鸡蛋🥚,这盒鸡蛋是不是整整齐齐地排成一排?每个鸡蛋都有自己的位置(第1个、第2个、第3个...),而且这些格子的大小都是一样的。
这就是数组! 一组连续的"格子",每个格子大小相同,能存放同一类型的数据。
超市的鸡蛋盒:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ 🥚│ 🥚│ 🥚│ 🥚│ 🥚│ 🥚│ 🥚│ 🥚│
└───┴───┴───┴───┴───┴───┴───┴───┘
0 1 2 3 4 5 6 7 ← 索引(从0开始)
🔍 数组的本质原理
1️⃣ 连续内存空间
数组在内存中是一块连续的空间,就像是一排紧挨着的房间 🏠🏠🏠。
内存地址: 1000 1004 1008 1012 1016
┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘
数组索引: [0] [1] [2] [3] [4]
假设每个整数占4个字节:
arr[0]在地址 1000arr[1]在地址 1004arr[2]在地址 1008- ...以此类推
2️⃣ 超快的随机访问(O(1)时间复杂度)
想要访问第i个元素?计算机只需要一个简单的数学公式:
元素地址 = 起始地址 + i × 元素大小
就像你知道你的座位号是"第8排第5座",你能直接走过去,不需要从第1排开始数!🎬
时间复杂度:O(1) - 超级快!⚡
💪 数组的优点 vs 😰 数组的缺点
✅ 优点:
| 优点 | 说明 | 生活比喻 |
|---|---|---|
| 🚀 访问快 | 通过索引直接访问,O(1)时间 | 知道门牌号直接找到房间 |
| 💾 缓存友好 | 连续内存,CPU缓存命中率高 | 书架上的书一本挨一本,拿起来方便 |
| 🎯 简单直观 | 最基础、最易理解的数据结构 | 小学生都能懂的"排队" |
❌ 缺点:
| 缺点 | 说明 | 生活比喻 |
|---|---|---|
| 📏 固定大小 | 一旦创建,大小不可变(Java) | 鸡蛋盒就这么大,多了放不下 |
| 🐌 插入删除慢 | 中间插入/删除需要移动元素,O(n)时间 | 队伍中间插队,后面的人都要往后挪 |
| 💸 空间浪费 | 预分配大小,可能用不完 | 买了12个格子的鸡蛋盒,只装了5个鸡蛋 |
🎬 数组的操作详解
1. 创建数组
// 方式1:声明并指定大小
int[] arr = new int[5]; // [0, 0, 0, 0, 0]
// 方式2:声明并初始化
int[] arr = {10, 20, 30, 40, 50};
// 方式3:先声明后赋值
int[] arr = new int[3];
arr[0] = 100;
arr[1] = 200;
arr[2] = 300;
2. 访问元素 - 超快!⚡
int[] scores = {85, 92, 78, 95, 88};
System.out.println(scores[0]); // 输出: 85 (第1个元素)
System.out.println(scores[3]); // 输出: 95 (第4个元素)
// ⚠️ 注意:索引从0开始!
时间复杂度:O(1)
3. 修改元素 - 也超快!⚡
scores[2] = 99; // 把第3个元素改成99
时间复杂度:O(1)
4. 插入元素 - 有点慢... 🐌
假设你要在位置2插入一个新元素:
原数组:
┌────┬────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │ │
└────┴────┴────┴────┴────┴────┘
0 1 2 3 4 5
在索引2插入25:
步骤1:把30、40、50往后挪 ➡️
步骤2:在位置2放入25
结果:
┌────┬────┬────┬────┬────┬────┐
│ 10 │ 20 │ 25 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┴────┘
0 1 2 3 4 5
public static int[] insert(int[] arr, int index, int value) {
int[] newArr = new int[arr.length + 1];
// 复制index之前的元素
for (int i = 0; i < index; i++) {
newArr[i] = arr[i];
}
// 插入新元素
newArr[index] = value;
// 复制index之后的元素
for (int i = index; i < arr.length; i++) {
newArr[i + 1] = arr[i];
}
return newArr;
}
时间复杂度:O(n) - n是数组长度
5. 删除元素 - 也有点慢... 🐌
原数组:
┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘
0 1 2 3 4
删除索引2的元素:
步骤1:把40、50往前挪 ⬅️
结果:
┌────┬────┬────┬────┐
│ 10 │ 20 │ 40 │ 50 │
└────┴────┴────┴────┘
0 1 2 3
时间复杂度:O(n)
🎯 数组的使用场景
✅ 适合用数组的场景:
-
数据量固定 - 比如一周7天的气温 🌡️
double[] weekTemperature = new double[7]; -
需要频繁随机访问 - 比如学生成绩查询 📊
int[] studentScores = {85, 90, 78, 92, 88}; int score = studentScores[3]; // 快速查询第4个学生的成绩 -
顺序遍历 - 比如打印所有元素 🖨️
for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); }
❌ 不适合用数组的场景:
- 频繁插入删除 - 用链表(LinkedList)更好 🔗
- 数据量不确定 - 用动态数组(ArrayList)更好 📈
- 需要快速查找 - 用哈希表(HashMap)更好 🔍
🌟 进阶:动态数组(ArrayList)
Java的数组大小固定很不方便?别担心,Java提供了ArrayList(动态数组)!
import java.util.ArrayList;
ArrayList<Integer> list = new ArrayList<>();
list.add(10); // [10]
list.add(20); // [10, 20]
list.add(30); // [10, 20, 30]
list.remove(1); // [10, 30] 删除索引1的元素
System.out.println(list.get(0)); // 输出: 10
🔍 ArrayList的扩容机制(面试重点!⭐)
ArrayList底层还是数组,但它会自动扩容:
初始容量:10
扩容策略:当数组满了,创建一个新数组,大小是原来的1.5倍
然后把旧数据复制过去
原数组(容量10):
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│10│20│30│40│50│60│70│80│90│满│
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
扩容后(容量15):
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│10│20│30│40│50│60│70│80│90│满│ │ │ │ │ │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
💡 经典面试题
1. 两数之和(LeetCode 1)
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数的索引。
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
return new int[0];
}
2. 最大子数组和(Kadane算法)
public int maxSubArray(int[] nums) {
int maxSum = nums[0];
int currentSum = nums[0];
for (int i = 1; i < nums.length; i++) {
currentSum = Math.max(nums[i], currentSum + nums[i]);
maxSum = Math.max(maxSum, currentSum);
}
return maxSum;
}
📝 总结
| 特性 | 数组 | 说明 |
|---|---|---|
| 访问速度 | ⚡⚡⚡⚡⚡ O(1) | 超快! |
| 插入删除 | 🐌🐌 O(n) | 慢... |
| 空间利用 | 😐 固定大小 | 可能浪费 |
| 适用场景 | 🎯 固定大小、频繁访问 | 最基础的结构 |
🎓 记忆口诀
数组就像鸡蛋盒,
格子连续排成行。
访问超快用索引,
插删缓慢要移动。
大小固定不灵活,
ArrayList来帮忙!
🚀 下一步学习
- 掌握了数组,接下来可以学习:
- 链表(LinkedList) - 解决数组插入删除慢的问题 🔗
- 栈(Stack) - 后进先出的魔法 📚
- 队列(Queue) - 先进先出的排队艺术 🎫
恭喜你!🎉 你已经掌握了程序员的第一个数据结构朋友——数组!
记住:数组虽然简单,但它是几乎所有高级数据结构的基础。就像学武功,基本功扎实了,才能练成绝世武功!💪
📌 小贴士:建议手写一遍数组的插入和删除操作,加深理解!
😊 有问题?记住:程序员最好的朋友是Google和Stack Overflow!