ArrayList的扩容机制
以下内容均基于JDK17
ArrayList的扩容会由5个方法触发
add(E e)
add(int index, E e)
addAll(Collection<? extends E> collection)
addAll(int index, Collection<? extends> collection)
ensureCapacity(int minCapacity)
与HashMap的扩容有所不同, ArrayList的扩容没有所谓的负载因子, 只是将加入元素后的实际size和当前容量比较,如果超出就会进行扩容
其主要操作是(以下size表示实际存储的数据量的数量,capacity表示内部动态数组elementData的容量):
如果采用默认的无参构造器,初始化的时候size和capacity实质都是0, 会在第一次加入元素m个后扩容至max(10, m)
否则会扩容至capacity + max(capacity >> 2 , m) (m是加入一波元素后超出原来的容量的个数),也就是扩容至1.5倍,如果不够就扩容到刚刚好够用为止
其中
add(E e)
addAll(Collection<? extends E> collection)
是在内部数组实际存储的尾部添加元素,不会有数据的位置调整
add(int index, E e)
addAll(int index, Collection<? extends> collection)
会导致原本的数据从index开始往后移动n位, 可能导致低效
ensureCapacity(int minCapacity)
这个方法的功能在于确保内部容量有minCapacity个,如果没有就触发1.5倍扩容机制,扩容到max((int)(1.5 * capacity), minCapacity)
需要注意的是,如果这个ArrayList刚刚经过无参构造初始化, 且minCapacity < 10, 不会进行扩容, 因为即使现在扩容也和加入1个元素后的扩容效果一样, 没有意义
@Test
public void testArrayListGrow() {
List<Integer> list = new ArrayList<>(); // 无参构造器构造出来的, 此时内部数组elementData={}, 容量为0, size也为0
for (int i = 1; i < 30; i++) {
list.add(Integer.valueOf(i)); // 断点Condition: i == 1 || i == 11 || i == 16 || i == 23
// i = 1 容量从0 -> 10
// i = 11 容量从10 -> 15
// i = 16 容量从 15 -> 22
// i = 23 容量从 22 -> 33 ...
}
list = new ArrayList<>(1);
for (int i = 1; i < 30; i++) {
list.add(Integer.valueOf(i)); // 断点Condition: i == 2 || i == 3 || i == 4 || i == 5 || i == 7 || i == 10 || i == 14 || i == 20 || i == 29
// i = 2 容量 1 -> 2
// i = 3 容量 2 -> 3
// i = 4 容量从3 -> 4
// i = 5 容量从4 -> 6
// i = 7 容量从6 -> 9
// i = 10 容量从9 -> 13
// i = 14 容量从13 -> 19
// i = 20 容量从19 -> 28
// i = 29 容量从28 -> 42
}
用上面的代码进行调试单步进入底层add可以看到扩容操作