1.类图
文章使用的源码是JDK 1.8.0_181版本
分析主要从下面几个点入手:
- 体系结构
- 构造方法
- 和LinkedList,Vector的区别
- 扩容
2.ArrayList的体系结构
ArrayList直接实现了下面这几个接口:
- List 有序集合接口,定义了一些基础方法,自实现了sort方法和replaceAll方法
- RandomAccess 标记接口,实现这个接口可以实现快速随机访问
- Cloneable 用来重写clone方法
- java.io.Serializable 序列化接口
- 继承AbstractList
3.构造方法
字段属性
ArrayList提供了三个构造方法,在解析这几个构造方法之前,需要先说一下ArrayList中的几个重要的属性。
/**
* Default initial capacity.
默认初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
用于空实例的共享空数组实例。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
用于默认大小的空实例的共享空数组实例。 我们将此与EMPTY_ELEMENTDATA区分开来,以便了解在添加第一个元素时要膨胀多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
存储ArrayList元素的数组缓冲区.ArrayList的容量是此数组缓冲区的长度。添加第一个元素时,任何带有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList都将扩展为DEFAULT_CAPACITY。
这个数组是ArrayList对象实际保存数据的地方
*/
transient Object[] elementData; // 非私有,以简化嵌套类访问
/**
* The size of the ArrayList (the number of elements it contains).
* ArrayList的大小(它包含的元素数)。
* @serial
*/
private int size;
无参构造
这个构造方法对应的实例化方法是下面这种写法,也是最常见的写法。
方法逻辑
无参构造里对elementData数组进行实例化,将elementData数组指向常量空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,长度为0,当第一次添加数据时,数组的长度会变成10。
对象创建时的elementData数组的长度
添加一个元素之后elementData数组的长度
指定初始容量的构造方法
对应下面这种写法:
方法逻辑
指定长度如果大于0,将elementData数组实例化为指定长度的数组。
指定长度如果等于0,将elementData指向长度为0的空数组常量。
指定长度如果小于0,抛出 IllegalArgumentException 异常
指定长度初始化之后elementData数组的长度:
添加元素之后elementData数组的长度:
可以看到指定长度初始化的ArrayList对象在添加元素之后elementData数组长度也不会变化。
使用Collection对象构造
这种写法比较多,这里给出一个简单的例子
方法逻辑
传入一个实现了Collection接口的对象(下文简称为c),然后将这个对象转成数组,并且将elementData数组指向这个这个数组。然后判断elementData数组的长度是否等于0,如果不等于0,并且c.toArray()返回的不是Object[]对象,则把c的内容copy到elementData中。如果长度等于0,则将elementData指向一个空数组。
-
- 和LinkedList,Vector的区别
这是一个经典的面试题和笔试题。先说一下这三个集合类的相同点。
相同点:
- 都实现了Collection,List,Serializable接口
- ArrayList和Vector都是使用数组存储数据
- 都是有序集合
- ArrayList和LinkedList都是线程不安全的
不同点:
- ArrayList是基于动态数组的数据结构,LinkedList是基于双向链表数据结构
- Vector是线程安全的
- 随机get和set时ArrayList 性能要优于LinkedList
- add和remove时LinkedList 性能要优于ArrayList
5.扩容
add方法的源码
可以看到在add方法之前调用了ensureCapacityInternal方法,传入的参数是size+1,size就是当前集合中的参数个数,可以确定这个方法就是扩容的方法了
在calculateCapacity 方法中先判断了elementData数组是否为空,如果为空判断则返回minCapacity 和10中最大的数值,如果不为空,则直接返回minCapacity。然后进入到ensureExplicitCapacity方法,对modCount做++操作,这个参数用于集合的Fail-Fast机制判断中。方法里判断新增元素之后大小是否超过当前集合元素个数,如果超过,则调用grow方法。
这个方法就是真正用于集合扩容的方法了,可以看到集合新容量的计算公式为:
新容量 = 现有容量 + 现有容量 / 2
如果新容量小于minCapacity ,则新长度等于minCapacity,如果新容量大于最大长度(2147483639),调用hugeCapacity 方法,传入参数为minCapacity,也就是数组扩容后需要最小容量。如果minCapacity大于MAX_ARRAY_SIZE,则返回Integer的最大值。否则返回MAX_ARRAY_SIZE。
调用完grow方法之后将elementData中的元素复制到一个新的大容量数组中。Arrays.copy方法是 System.arraycopy 方法。因为方法是native修饰的,所以无法查看源码。