ArrayList的扩容机制
ArrayList是一个动态数组,在使用的过程中它会去根据情况动态的改变集合的大小。
- ArrayList的默认初始容量为10,注意,当我们刚创建一个ArrayList的时候,它的容量大小为0,只有在执行第一次add操作时,才会扩容到10;
- 当然,ArrayList也提供了一个自定义初始容量大小的构造方法,我们可以通过改方法自定义ArrayList的初始容量;
- ArrayList的扩容机制,当我们在执行add操作时,发现当前集合容量大小不够了,会扩容到原容量的1.5倍,也就是增加原容量的0.5倍大小,比如大小为10,扩容后为15。
源码分析ArrayList的扩容机制
用下面这段代码来分析:
public class App {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
// 给当前集合添加10个元素
for (int i = 0; i < 10; i++) {
list.add(i);
}
// 继续添加5个
for (int i = 10; i < 15; i++) {
list.add(i);
}
}
}
先来看一下ArrayList中的成员变量
第一次add操作,如何初始化一个大小为10的集合的
上述代码,首先会先调用ArrayList的无参构造方法,去初始化一个ArrayList
可以看到,当我们执行完无参构造器时,只是去初始化了一个空的elementData数组,这个时候并没有将默认大小10给它。
这个时候,进入for循环,执行第一次list.add
方法,观察源码,看看它底层执行了什么。
进入add
方法,首页会执行ensureCapacityInternal(size + 1);
方法
此时,因为我们第一次执行add方法,集合还是为空的,所以这里的size为0。
step into —> ensureCapacityInternal
方法
先是进行了判断,如果当前数组为空,则在DEFAULT_CAPACITY和minCapacity之间取一个最大值重新赋值给minCapacity。
其实这里的minCapacity就是指当前集合需要的最小容量,比如这里我们执行第一次
add
方法,当前集合容量为空,那我们当前集合需要的最小容量就是1,执行二次add
方法,也就是当前集合容量为1了,那当前集合需要的最小容量就是2。
这一步也证明了,我们在执行第一次add
方法才将集合初始化为它的默认大小10(DEFAULT_CAPACITY)。
继续往下执行,我们step into 到ensureExplicitCapacity
方法。
这里的modCount用来记录当前集合被修改过多少次。
主要看下面的if判断,它的意思就是说,如果我们集合最小需要的容量已经大于当前存储集合元素的数组的容量了,那么就去执行grow
方法,进行扩容。
很好理解,不扩容就放不下了这个元素了。
集合扩容机制的核心代码就在grow
方法里面。
step into ----> grow
方法:
- 首先,先将当前的数组大小赋值给oldCapacity变量,因为这里执行的是第一次添加,所以大小为0;
(oldCapacity >> 1)
,这段代码的意思就是将oldCapacity / 2,int newCapacity = oldCapacity + (oldCapacity >> 1)
这段代码就体现了我们上述所说的,扩容到原集合小大的1.5倍(这里无法体现出来,因为第一次执行add操作), 这里执行完毕后,newCapacity == 0,还是那句话,因为是第一次执行add方法,elementData是没有元素的,所以这里0+ 0/2还是为0;- 所以,这里就进行了下一步判断
if (newCapacity - MAX_ARRAY_SIZE > 0)
,如果newCapacity小于minCapacity的话,那么就把minCapacity的赋值给newCapacity,这就避免了newCapacity == 0的情况; if (newCapacity - MAX_ARRAY_SIZE > 0)
,这种情况是说当我们集合容量大到超过了MAX_ARRAY_SIZE(2147483679)时,才去执行的方法,可以先不看这里,这种情况毕竟不多;- 最后执行了
elementData = Arrays.copyOf(elementData, newCapacity)
,数组克隆,也就是返回一个数组,这个数组的大小为newCapacity,并且保留原数组elementData中的内容。
执行完这一步,我们发现,此时elementData的已经被初始化为一个容量为10的数组了。
继续往下执行,回到我们的add
方法
将元素添加到数组中。
当集合容量不够时,再来看看源码
为了更好的看清扩容的实现,我们进行第二个for循环操作,也就是说,目前我们list集合中已经存在了10个元素,已经全部占用其默认的集合大小了,如果在向list的保存元素,那么它就需要进行扩容了。
debug:
这里还是一样,先执行ensureCapacityInternal
:
此时,我们发现当前minCapacity为11,在解释一下,因为我们要在原来容量为10的集合中在添加当前元素,所以这里集合最少需要的容量为11;elementData此时是有10个元素的,所以这里的if判断为false,直接去执行我们的
ensureExplicitCapacity
方法
在执行if (minCapacity - elementData.length > 0)
判断的时候,我们发现,此时集合最少需要的容量为11,已经超过当前集合容量大小10了,所以我们要对其进行扩容了,执行grow
方法。
这里就能明显的看出来了,在扩容后的数组容量是原数组容量的1.5倍,oldCapacity + (oldCapacity >> 1) -----> 10 + 10/2 = 15
,所以在执行完copyof
方法之后,该数组已经被扩容了。
自定义ArrayList的大小
直接通过ArrayList提供的有参构造方法即可实现自定义初始化容量大小:
ArrayList<Integer> list = new ArrayList<>(8);
与上面不同的时候,这里只要执行完new ArrayList<>(8)
后,不需要执行第一次add方法,其初始化大小就为8了。
具体的扩容机制还是同上述一样,如果当前容量8已经装不下了,那么就会扩容到12 —> 18 ----> 27…