【源码】走一遍源码弄清ArrayList容器的扩容机制
首先我们来看看ArraysList容器在整个Java集合框架中所处的位置
由此可见ArrayList是Java集合框架中,两大派系中Collection
接口的子接口List
的实现类
我们从源码入手可以看到ArrayList底层数据结构实际上是一个**Object
类型的数组**,由于Object是所有类的父类,所以ArrayList集合可以存放任意类型的元素,并且存放的元素是有序的、可重复的、可随机访问的。
接下来我们就以add()
方法为突破口,来解读一下ArrayList的源码,以及它的扩容机制
我们要想向ArrayList结合添加元素,首先得创建出一个ArrayList对象出来,ArrayList的构造方法有3种:
-
无参构造器
使用无参构造器创建一个对象时,默认创建的是一个空集合,它的初始容量是
10
。什么鬼?空集合怎么还容量为10,这不自相矛盾吗?我这里说的空集合指的是,这个集合刚创建出来,在还没有调用add()方法向集合中添加第一个元素之前,这个集合是空的。而所说的初始容量为10,指的是当你调用add()方法向集合中添加第一个元素时,这个集合的容量大小会被扩容到10,这个10就是指的初始容量。可以我们为向容器中添加了一个元素,为什么要扩容到10呢?这不浪费内存空间吗?实际上这是考虑的扩容的问题,你创建这个容器肯定不止存放一个元素,我们之所以冗余了一些空间,就是为了防止不断频繁扩容导致性能低下的问题。
-
带初始容量参数的构造函数
我们在创建一个容器时,在构造方法中可以传入初始容量大小,如果传入的初始容量大小为0的话,就会创建一个空的容器。
注意!这里的
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
和无参构造方法中的EMPTY_ELEMENTDATA
虽然都是空集合,但是这两个空集合是有区别的,DEFAULTCAPACITY_EMPTY_ELEMENTDATA
在添加第一个元素时,明确指定了扩容到默认的初始容量大小。而EMPTY_ELEMENTDATA
由于指定的初始容量为0,所以在扩容的时候,会调用grow()
方法走扩容的流程。
-
传入一个集合参数的构造器
在创建ArrayList容器时,可以给构造器传入一个集合参数,利用
Arrays.copy()
方法复制这些集合中元素,原来创建一个新的集合
现在我们利用构造器创建出来了一个ArrayList容器,接下来就调用add()
方法开始向容器中添加元素。
在向容器中添加元素时,首先会调用ensureCapacityInternal()
方法确保容器容量足够,得到最小扩容量
如果是使用默认的无参构造器创建的容器,并且是添加第一个元素,minCapacity 为 10,也就是当前添加元素的操作,需要容器的容量大小为minCapacity
确定了最小容器容量之后,就调用ensureExplicitCapacity()
方法根据得到的最小容量来判断到底需不需要进行扩容
只有当需要的最小容量大于当前容器的最大容量时,最大的容量也就是数组的长度,就会调用grow()
方法进行扩容
从grow()
方法可以看出ArrayList容器的扩容机制是变为原来容量的1.5
倍
为了防止在添加第一个元素时,由于空集合的容量为0,扩大1.5倍之后计算出的新的容量还是0,这样情况,源码中还通过比较扩容后的新的容量与需要的最小容量,来判断是否是第一次添加元素,如果是就直接将扩容后的新的容量改成需要的最小容量。这样就避免了第一次扩容扩不起来的问题。
如果说扩容后的容量超过了ArrayList的最大容量,也就是扩容1.5倍后的容量大于了Integer.MAX_VALUE - 8
。此时就需要判断需要的最小容量是否超过了ArrayList的最大容量。
如果需要的最小容量超过了ArrayList最大容量,此时我们就不能按照1.5倍扩容了,直接返回Integer.Max_VALUE,如果说没有超过ArrayList最大容量,就返回最大容量
扩容完之后,就调用Arrays.copyOf()
方法,开始将原来容器中的所有元素复制到扩容后的容量中
Arrays.copyOf()
方法底层调用的实际上是System.arraycopy()本地方法,将原数组中的数据进行拷贝,并返回新的数组
至此,ArrayList容器的扩容流程就完成了!
我们注意到ArrayList的增删改查相关的方法都没有使用Synchronized
同步关键字,或者说没有使用其他同步机制,因此ArrayList容器是线程不安全的!
与ArrayList
容器对标的是Vector
这个容器
Vector的底层实现和ArrayList的底层实现类似,也是使用Object类型的数组存放元素。而且默认初始容量也为10
与ArrayList不同的地方在于Vector的扩容的方法使用了Synchronized
同步关键字,因此是**线程安全的!**而且Vector扩容机制是扩大2
倍!而ArrayList是扩大1.5
倍!
一般开发中都是使用ArrayList容器,Vector容器基本上不再使用,虽然Vector使用Synchronized保证了线程安全,但是效率十分低下,如果考虑到线程安全问题,可以使用JUC并发集合!