提起ArrayList,新手总觉得它就是个"自动扩容的数组",随便add、get都不怕------直到遇到 IndexOutOfBoundsException ,才发现这货藏着不少小心机。今天咱扒一扒ArrayList的扩容黑科技,以及那些让你踩坑的细节!
一、先搞懂:ArrayList为啥能"自动变长"?
ArrayList底层是普通数组,默认初始容量是10。当添加元素导致数组满了,它会悄悄扩容:新容量=旧容量×1.5(源码里是 oldCapacity + (oldCapacity >> 1) ),然后把旧数组的数据复制到新数组。
看个简单例子,直观感受扩容:
java
import java.util.ArrayList;
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(); // 初始容量10
// 加10个元素,刚好填满初始数组
for (int i = 0; i < 10; i++) {
list.add("元素" + i);
}
// 第11个元素:触发扩容!新容量变成15
list.add("触发扩容的元素");
System.out.println("当前元素数量:" + list.size()); // 输出11
System.out.println("能存但未用的容量:" + (list.size() < 15 ? 15 - list.size() : "已再次扩容")); // 输出4
}
}
二、踩坑重灾区:size和capacity别搞混!
很多人以为 list.size() 是数组容量,其实大错特错:
size():实际存储的元素数量(你add了多少个)capacity:底层数组的长度(ArrayList内部用 elementData 数组,容量不对外暴露)
这就是为啥 get(10) 会报错------哪怕数组容量是15,只要你只存了11个元素,索引10是有效的,但索引11就越界:
java
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 11; i++) {
list.add("元素" + i);
}
System.out.println(list.get(10)); // 正常输出:元素10
list.get(11); // 直接抛IndexOutOfBoundsException!
}
三、实用技巧:提前指定容量,避免频繁扩容
如果知道要存多少元素,初始化时直接指定容量,能减少数组复制的性能损耗:
java
// 已知要存1000个元素,直接指定容量
ArrayList<String> list = new ArrayList<>(1000);
// 后续add1000个元素,一次扩容都不会触发
最后划重点
-
ArrayList扩容是"偷偷摸摸"的,扩容后旧数组会被GC回收;
-
越界异常看的是
size(),不是底层数组容量; -
大数据量场景一定要指定初始容量,优化性能。