浅谈JDK1.8中ArrayList的动态扩容

浅谈JDK1.8中ArrayList的动态扩容


简介

ArrayList的底层,实际就是一个可以动态扩容的Object数组,它继承了AbstractList这个抽象类,实现了List, RandomAccess, Cloneable, java.io.Serializable四个接口。其中,值得一提的是RandomAccess这个接口。RandomAccess是一个标记接口,用于标明实现该接口的List支持快速随机访问,主要目的是使算法能够在随机和顺序访问的list中表现的更加高效(当要实现某些算法时,会判断当前类是否实现了RandomAccess接口)。

RandomAccess
这里写图片描述

构造器


ArrayList()
ArrayList(Collection c)
ArrayList(int initialCapacity)

无参构造器

我们先来看无参构造器的源码
浅谈JDK1.8中ArrayList的动态扩容_第1张图片
我们可以看到这里的注释写的是创建一个初始容量为10的list,但是实际上并不是在这一步实现的,我们可以看看DEFAULTCAPACITY_EMPTY_ELEMENTDATAelementData
浅谈JDK1.8中ArrayList的动态扩容_第2张图片
可以看到,在JDK1.8(1.7)中,当我们使用new ArrayList()这种方式来创建集合时,底层实际上是创建了一个空的Object数组。

有参构造器

浅谈JDK1.8中ArrayList的动态扩容_第3张图片
这个构造器会构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。 此时elementData是传入的集合转成的数组,size就是elementData数组的长度。size是ArrayList的一个私有成员变量,用来表示ArrayList中的元素个数,我们平时使用的size()方法,实际上返回的就是这个成员变量。

浅谈JDK1.8中ArrayList的动态扩容_第4张图片
这个构造器会构造一个具有指定初始容量的空列表。代码很简单,不做过多的分析。

ArrayList的动态扩容

我们在前面看到,JDK1.8的源码中,无参构造器的注释写的很清楚,会创建一个初始容量为10的数组,但是在构造器中并没有体现出来。这是因为从JDK1.7开始,调用无参构造时,只会创建一个空数组。只有调用add()或addAll()方法时,才会初始化数组的容量。
浅谈JDK1.8中ArrayList的动态扩容_第5张图片
ArrayList的add方法中,会调用ensureCapacityInternal()方法,我们继续查看这个方法的源码
这里写图片描述
其中会调用一个calculateCapacity()方法
浅谈JDK1.8中ArrayList的动态扩容_第6张图片
从前面的分析我们可以知道,当我们使用无参构造器或是指定初始容量为0时创建的集合,会创建一个空数组,且这个空数组就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此时我们方法的入参minCapacity为1,而DEFAULT_CAPACITY就是默认的数组初始化容量10。
这里写图片描述
因此,calculateCapacity()最终返回值为10,传入了ensureExplicitCapacity()方法。
浅谈JDK1.8中ArrayList的动态扩容_第7张图片
当数组最小所需容量大于当前数组的容量时,就会调用grow()方法来对数组进行扩容。我们查看该方法代码:
浅谈JDK1.8中ArrayList的动态扩容_第8张图片
oldCapacity为扩容前数组的长度,newCapacity是在原数组长度的基础上,加上了(oldCapacity >> 1),右移一位相当于除以2,即newCapacity = oldCapacity + 0.5oldCapacity 。也就是说容量扩大为了原来的1.5倍。
如果扩大后的容量,依然不满足要求,小于数组所需最小容量,那么就直接将数组容量扩大到所需最小容量:

if (newCapacity - minCapacity < 0)
   newCapacity = minCapacity;

如果扩大的容量超过了最大容量MAX_ARRAY_SIZE,就要调用hugeCapacity()方法,根据数组最小所需容量来确定扩容后的容量:

if (newCapacity - MAX_ARRAY_SIZE > 0)
   newCapacity = hugeCapacity(minCapacity);

浅谈JDK1.8中ArrayList的动态扩容_第9张图片
如果数组最小所需容量大于MAX_ARRAY_SIZE,就返回Integer.MAX_VALUE,否则就返回MAX_ARRAY_SIZE
然后回到grow方法,调用Arrays.copyof方法,即复制原数组内容到一个新容量的大数组里。这里Arrays.copyof方法实际是调用System.arraycopy方法。

总结

到这里我们就分析完了ArrayList的动态扩容机制:

  • 使用无参构造器创建的ArrayList初始容量为0,第一次调用add()/addAll()方法时才会初始化数组的容量,初始容量为10
  • 对集合添加若干个元素时,如果当前集合的容量满足需求,不扩容;如果当前集合容量不满足需求,则扩大为原来的1.5倍;如果扩大1.5倍依然不满足需求,则扩大为满足需求的最小容量。

你可能感兴趣的:(Java)