arraylist-grow
以前一直想当然的以为arrayList是双倍扩容(跟HashMap不一样),今天总结一下。
以下基于jdk8分析
1 | public boolean add(E e) { |
可以看到,在ensureCapacityInternal之后就可以进行数组的插入了,而且插入后将size加1了。后一个*size++*容易理解,size记录数组的长度,赋值完元素后加1操作,这里有几个实例变量,modCount
、size
、elementData
。
modCount
,继承于AbstractList,用于记录着集合的修改次数,也就是每次add或remove都加1. 是fail-fast机制。在初始化迭代器时,modCount的值会赋给expectedModCount,在迭代的过程中,只要modCount改变了,造成两者不一致,就会抛出currentModificationExpections。size
,数组中包含元素的个数elementData
,数组,也就是ArrayList底层实际存储数据的地方
确认容量
1 | private void ensureCapacityInternal(int minCapacity) { |
这个方法内将他分为两步
计算capacity
1 | private static int calculateCapacity(Object[] elementData, int minCapacity) { |
怎么计算呢?判断是否是空的,如果是的话就赋予默认的容量,否则就返回入参minCapacity,所以,这一步就是为初始化(未指定大小)来做的。
明确capacity
1 | private void ensureExplicitCapacity(int minCapacity) { |
add操作,增加modCount来记录。由于传入的minCapacity是size+1,size是元素的个数(不是数组长度,初始10个坑位,可以只存2个元素),size+1表示期望的元素数量。紧接着判断期望的数量是否大于数组长度,如果大于的话执行执行grow方法。
扩容
1 | private void grow(int minCapacity) { |
不是想象中简单的两倍,但是最终是一样的,拿到一个新的数组,将旧数组的元素复制过去,那么, 新的数组长度如何确认呢?关键就在这个方法中了。
oldCapacity
:原始数组长度newCapacity
:原始数组长度 + 原始数组长度/2 (也就是1.5倍的原始数组长度)
上面两个是初始定义的参数,除了这两个,还会传入一个参数minCapacity
,这里会有个问题,按照定义的参数,新数组能满足期望的长度么?按理说每次都只是加一个元素,是可以满足的吧?代码中有后续的判断:
- 如果新的容量小于期望容量,那么就将期望容量作为新的容量
- 如果新的容量大于最大的数组容量,调用hugeCapacity来生成新的容量。新的容量有两种来源,一种是1.5倍的原始长度,另一种是被期望长度赋予,无论哪种,这里都要判断是否大于最大值。如果大于的话拿期望值作为参数去计算新的容量。这里也有两种可能:
- 1.5倍的原始长度符合期望,但是大于了最大长度,所以不能1.5倍的计算,要拿期望长度重新计算
- 1.5倍的原始长度不符合期望,直接拿期望长度作为新的长度,但是新的长度大于了最大长度,所以拿期望长度重新计算
1 | private static int hugeCapacity(int minCapacity) { |
大于最大值如何解决?小于0 的话溢出(size+1),抛出内存溢出,大于的话直接拿整型的最大值作为返回值,等于就返回最大值。
执行加操作
经过上一步,应该有位置放元素了,正常添加,然后size计数。