一、ArrayList 核心定义
ArrayList 是 Java 集合框架中 List 接口的动态数组实现 (基于数组扩容),位于 java.util 包下,默认实现了 RandomAccess(支持随机访问)、Cloneable(可克隆)、Serializable(可序列化)接口,线程不安全。
二、底层原理
- 存储结构 :底层基于Object [] 数组 (elementData)存储元素,JDK 7 中数组默认初始容量为 10(空参构造直接初始化
new Object[10]),JDK 8 做了优化(空参构造初始化空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,首次添加元素时才扩容为 10,节省内存)。 - 扩容机制 (核心面试点):
- 触发条件:当添加元素时,数组容量不足(size == elementData.length);
- 扩容流程:
- 计算新容量:默认扩容为原容量的
1.5 倍(oldCapacity + (oldCapacity >> 1)); - 数组拷贝:通过
Arrays.copyOf()将原数组元素拷贝到新数组(底层是 System.arraycopy () 本地方法);
- 计算新容量:默认扩容为原容量的
- 手动扩容:可通过
ensureCapacity(int minCapacity)提前指定容量,减少多次扩容的性能损耗。
三、核心特性
表格
| 特性 | 说明 |
|---|---|
| 随机访问 | 支持通过索引快速访问(时间复杂度 O (1)),因为数组内存地址连续 |
| 增删效率 | 尾部增删:O (1)(无扩容时);非尾部增删:O (n)(需移动元素) |
| 线程安全 | 线程不安全(未加同步锁),多线程场景需使用 Collections.synchronizedList() 或 CopyOnWriteArrayList |
| 允许 null 值 | 可存储 null 元素(可通过 indexOf (null) 查找 null 位置) |
| 有序性 | 元素按插入顺序存储,索引从 0 开始 |
| 容量可动态调整 | 区别于普通数组(固定长度),ArrayList 可通过扩容自动调整容量 |
四、常用核心方法
java
运行
// 1. 初始化
List<String> list1 = new ArrayList<>(); // JDK8 空数组,首次添加扩容为10
List<String> list2 = new ArrayList<>(20); // 指定初始容量20
List<String> list3 = new ArrayList<>(Arrays.asList("a", "b")); // 基于集合初始化
// 2. 增删改查
list1.add("java"); // 尾部添加(O(1))
list1.add(1, "python"); // 指定索引添加(O(n))
list1.remove(0); // 按索引删除(O(n))
list1.remove("java"); // 按元素删除(需遍历,O(n))
list1.set(0, "c++"); // 修改指定索引元素(O(1))
String elem = list1.get(0); // 随机访问(O(1))
int index = list1.indexOf("python"); // 查找元素索引(O(n))
// 3. 扩容相关
list1.ensureCapacity(100); // 手动指定最小容量,避免多次扩容
list1.trimToSize(); // 缩容:将数组容量调整为当前元素个数(释放多余内存)
// 4. 遍历(推荐方式)
// 方式1:for循环(随机访问效率高)
for (int i = 0; i < list1.size(); i++) {
System.out.println(list1.get(i));
}
// 方式2:增强for循环(底层是迭代器)
for (String s : list1) {
System.out.println(s);
}
// 方式3:迭代器(支持遍历中删除元素)
Iterator<String> it = list1.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("python")) {
it.remove(); // 正确删除方式(避免ConcurrentModificationException)
}
}
五、面试高频易错点
-
遍历中删除元素的坑 :
- 错误:增强 for 循环中直接调用
list.remove()→ 抛出ConcurrentModificationException(快速失败机制,modCount != expectedModCount); - 正确:使用迭代器的
it.remove()或普通 for 循环倒序删除。
- 错误:增强 for 循环中直接调用
-
ArrayList vs LinkedList 区别 (高频对比):
表格
维度 ArrayList LinkedList 底层结构 数组 双向链表(Node 节点) 随机访问 O (1)(快) O (n)(慢,需遍历) 增删效率 非尾部 O (n) 首尾增删 O (1),中间 O (n) 内存占用 连续内存,有扩容冗余 每个节点存储前后指针,内存开销大 适用场景 频繁查询、少量增删 频繁增删、少量查询 -
JDK 7 vs JDK 8 初始化差异 :
- JDK 7:空参构造 → 直接创建长度为 10 的数组;
- JDK 8:空参构造 → 初始化空数组,首次 add 元素时才扩容为 10(懒加载)。
-
subList () 陷阱 :
list.subList(from, to)返回的是原数组的视图(非新数组),修改子列表会同步修改原列表;- 子列表持有原列表引用,原列表扩容后(数组地址变化),子列表操作会抛异常。
六、使用场景
- 适合频繁查询、少量增删的场景(如数据展示、排行榜);
- 不适合高频增删(非尾部) 或多线程并发修改的场景(后者优先用 CopyOnWriteArrayList)。
总结
ArrayList 核心关键点:
- 底层是动态扩容的 Object 数组,默认扩容 1.5 倍,随机访问效率高、非尾部增删效率低;
- 线程不安全,遍历删除需用迭代器,避免 ConcurrentModificationException;
- JDK 8 优化了初始化逻辑(懒加载),与 LinkedList 核心区别在底层结构和访问 / 增删效率。