 
  Java源码之旅(1) - ArrayList
技术在学习中成长,源码的世界没有你想象的那么复杂
前言
2018年的五月,开始java的源码学习之旅,从简单的角度去理解java的源码,前几天在学习交流中正好看了一下java集合的源码,才发现源码并没有想象中的那么难以理解,所以,源码之旅从java的集合类开始咯。
本章的源码版本为:JDK1.8
类的关系
要理解ArrayList的源码,我们就需要从它的关系开始,ArrayList继承了AbstractList,实现了List接口,我们从UML图可以看出:
虚线箭头表示实现接口,实线箭头表示继承关系
ArrayList简介
ArrayList是java中最常用的集合类了,说到ArrayList,我们不得不说说LinkedList,因为他们都是从Collection派生而来的,都是用来存放对象的序列的集合类,ArrayList相比与LinkedList有什么优劣呢?
- ArrayList:- 优点:随机访问元素的速度快 
- 缺点:从中间插入和移除元素比较慢 
 
- LinkedList:- 优点:从中间插入和移除元素速度快 
- 缺点:随机访问元素的速度比较慢 
 
接下来我们就从源码的角度去理解一下为什么有这些优缺点。
源码分析
ArrayList的初始化
ArrayList有三个构造方法
| 1 |  | 
从源码中我们可以看出,ArrayList其实底层使用的是数组,transient Object[] elementData 这个变量就是用来存放对象的一个数组。
- ArrayList的空参构造函数:也就是默认的构造函数,当- new ArrayList()的时候调用这个方法,可以看出将- elementData变量地址指向了- DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个初始容量为10,并且为空的元素数组;如步骤- (1)
- ArrayList的指定大小的构造函数:当初始化一个指定大小的- new ArrayList(int)的时候调用该方法,这个方法首先对- initialCapacity参数进行判断,如果大于0,那么创建一个指定大小的数组- (2);如果等于0,创建一个空的数组- (3);否则就判处异常;
- 初始化给定一个集合的构造函数:如果初始化的时候,给定一个集合对象,那么将这个集合转换为数组 - (4),然后对这个数组的长度进行判断,如果数组不等于0,那么调用- Arrays.copyOf(elementData, size, Object[].class)方法- (5),这个方法是一个核心方法,这个方法就是初始化一个大小为等于当前数组的一个新的数组,然后将对象- copy到新的数组中,然后将内存地址指定给- elementData,从下面的- Arrays.copyOf的源码可以看出来。
| 1 |  | 
copyOf方法首先判断两个对象的类型,如果类型一致,那么直接创建一个同大小的数组;如果类型不一致,则调用Array.newInstance指定类型进行初始化这个数组,当然,大小也是一致的; 最后调用System.arraycopy将参数数组copy到新的目标并返回。
ArrayList的常用方法之 add
我们先看一下源码:
| 1 |  | 
ArrayList有两个add方法,第一个方法就是按顺序将对象插入到尾部,第二个方法就是从中间插入对象。
add(E e) 方法首先会判断数组的容量是否超过极限ensureCapacityInternal(size + 1),这个方法首先会进行容量的判断,如果超过了极限,创建一个新的数组,大小是旧数组1.5倍,然后将旧数组中的对象全部拷贝到新的数组(1),等下会详细解析这个方法。最后将参数对象插入到数组中,返回true。
add(int index, E element)首先会调用rangeCheckForAdd(index)进行index的是否越界的验证(2),然后调用上一个方法中一样的判断容量是否超过极限的方法,下一步就是一个核心的方法System.arraycopy,这个方法我们在ArrayList初始化中已经讲过了,但是这里不太一样:
- elementData: 源数组
- index:源数组起始位置
- elementData:目标数组
- index + 1:目标数组起始位置
- size - index:复制数组元素数目
从源码中可以看出,当我们往一个ArrayList中间插入一个对象的时候,index索引处后面的索引往后移动一位,最后把索引为index空出来,并将element赋值给它。这样一来我们并不知道要插入哪个位置,所以会进行匹配那么它的时间赋值度就为n。
接下来看一下ensureCapacityInternal(size + 1)这个方法的调用链:
| 1 |  | 
ensureCapacityInternal(int minCapacity)方法中判断当前数组中的元素是否为空,如果为空则给定一个最大的值,然后调用ensureExplicitCapacity(minCapacity),这个方法主要是判数组容量是否超过极限,如果超过极限调用grow(int minCapacity),这个方法就是扩容方法,该方法会创建一个比原数组大1.5倍的新数组,然后将原数组中的所有对象copy到新的数组中。
ArrayList的常用方法之 remove
remove方法其实跟从中间插入对象的add方法有很大的相似之处,如果我们删除某一个元素,将index开始后面的所有对象都往前移动一位,底层方法其实是复制一遍,所以删除一个对象的复杂度和从中间插入一个对象是差不多的。
| 1 |  | 
ArrayList的常用方法之 get
ArrayList的get方法非常直观的就能理解了,废话不多说,直接看代码:
| 1 |  | 
检查index合法性,然后从数组中取出对象并返回,是不是很简单呢?
总结
本章列举了一些ArrayList常用的方法,了解到ArrayList底层其实是一个对象数组,以及从中间插入对象和移除对象比较慢的原因,从这些方法出发,理解ArrayList其他的方法会很简单了。下一章讲一讲LinkedList的源码。
- 本文作者:winter chen
- 本文链接:https://blog.winterchen.com/2018/05/26/java-source-code-arraylist/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!

