Java 存储数据:数组 vs 集合(全面对比与使用指南)
在 Java 中,存储一组数据最常用的两种方式就是数组(Array)和集合(Collection)。
它们各有优势,理解它们的区别和适用场景,是 Java 开发者的基础功底。
1. 核心对比表(建议背下来)
| 维度 | 数组(Array) | 集合(Collection / List / Set / Map) | 胜出场景 |
|---|---|---|---|
| 长度/大小 | 固定长度(创建时确定,不可变) | 动态长度(可自动扩容/缩容) | 集合胜 |
| 数据类型 | 支持基本类型和引用类型(int[]、String[]) | 只能存储引用类型(包装类或对象),不支持基本类型 | 数组胜(基本类型场景) |
| 底层实现 | 连续内存块 | 多种实现(ArrayList 也是数组,LinkedList 是链表) | — |
| 访问效率 | get/set O(1) 极快 | ArrayList O(1),LinkedList O(n) | 数组 / ArrayList 胜 |
| 插入/删除效率 | 中间插入/删除需要移动元素 O(n) | ArrayList 中间 O(n),LinkedList O(1) | LinkedList 胜(频繁插入删除) |
| 是否可变 | 长度不可变,元素可变 | 长度可变,元素可变 | 集合胜 |
| 是否线程安全 | 非安全 | 大部分非安全(少数如 CopyOnWriteArrayList 安全) | 视具体实现 |
| 是否支持泛型 | 支持泛型(Java 5+) | 支持泛型(推荐使用) | — |
| 内存开销 | 较低(只有数据本身) | 较高(有额外结构、对象头、包装类) | 数组胜 |
| 功能丰富度 | 几乎无内置方法 | 非常丰富(contains、remove、sort、subList 等) | 集合胜 |
| 典型实现类 | int[]、String[]、Object[] | ArrayList、LinkedList、HashSet、HashMap 等 | — |
一句话总结:
- 数组:固定长度、高性能、基本类型友好,适合数据量确定且追求极致性能的场景
- 集合:动态、可扩展、功能强大,适合绝大多数业务开发场景
2. 什么时候用数组?什么时候用集合?
强烈推荐使用集合的场景(90%+ 情况):
- 数据长度不确定或会变化
- 需要频繁增删改查
- 需要判断元素是否存在(contains)
- 需要排序、去重、子列表等高级操作
- 使用泛型提高类型安全
- 团队协作开发(代码可读性更高)
推荐使用数组的场景(少数但重要):
- 数据长度固定且已知(如一周7天、12个月)
- 需要存储基本类型且在意内存和性能(int[]、double[])
- 高性能计算场景(科学计算、游戏开发、实时系统)
- 与底层 API 交互(如 IO 操作、JNI、反射)
- 极致内存敏感场景(如海量数据处理)
3. 代码对比示例
// --------------------- 数组 ---------------------
int[] scores = new int[5]; // 固定长度
scores[0] = 85;
scores[1] = 92;
// scores[5] = 100; // 越界!ArrayIndexOutOfBoundsException
// 遍历
for (int i = 0; i < scores.length; i++) {
System.out.println(scores[i]);
}
// --------------------- 集合(ArrayList) ---------------------
import java.util.ArrayList;
import java.util.List;
List<Integer> scoreList = new ArrayList<>();
scoreList.add(85);
scoreList.add(92);
scoreList.add(78);
scoreList.add(100); // 长度自动增长
// 插入到中间
scoreList.add(1, 99); // 自动后移元素
// 删除
scoreList.remove(2); // 删除索引2的元素
// 判断是否存在
if (scoreList.contains(100)) {
System.out.println("有100分");
}
// 更方便的遍历
for (Integer score : scoreList) {
System.out.println(score);
}
// 排序
scoreList.sort(Integer::compareTo);
4. 常见误区与避坑
- 误区1:用数组存对象却频繁扩容
// 错误示范:频繁创建新数组 + 复制
String[] arr = new String[10];
// ... 满了再 new String[20] + System.arraycopy
正确:直接用 ArrayList<String>
- 误区2:基本类型用集合导致大量装箱
List<Integer> list = new ArrayList<>(); // 装箱开销
优化方案(大数据量时):
- 用第三方库:Trove、fastutil(intArrayList、IntArrayList)
- 或自己维护 int[]
- 误区3:数组当作集合用,却忘记长度固定
String[] names = new String[100];
int count = 0;
// 手动维护 count,很容易出错
- 误区4:多线程下直接用 ArrayList
正确做法:
Collections.synchronizedList(new ArrayList<>())CopyOnWriteArrayList(读多写少)Vector(不推荐)ConcurrentLinkedQueue(队列场景)
5. 快速选择指南
问自己这几个问题:
- 长度是否固定且已知? → 是 → 用数组
- 需要存储基本类型且性能敏感? → 是 → 用数组
- 需要动态增删、查找、排序等操作? → 是 → 用集合
- 是业务系统、Web、企业开发? → 99% 用集合(ArrayList 为主)
- 是高性能计算、游戏、嵌入式、大数据底层? → 考虑数组
总结口诀
“不确定长度、要方便操作,优先集合;长度固定、追求极致性能,用数组。”
大多数 Java 开发者日常 90%+ 的列表场景用的是 ArrayList,但理解数组与集合的底层差异,能让你在性能优化、面试、底层框架阅读时更有优势。
如果你想深入某个具体点,比如:
- ArrayList 扩容机制详解
- LinkedList vs ArrayList 真实性能对比
- 基本类型集合的第三方优化方案
- 多线程安全的 List 选择
随时告诉我,我可以继续展开讲解!