简介:
源码一般由大量代码组成,一上来就一行一行读可能会觉得难以读懂甚至放弃,这个时候不妨试试以debug的形式打开,一步一步来理解源码的运行机制,因此本文将主要使用Debug的形式对java.util.ArrayList的源码进行分析
1.前言
源码一般由大量代码组成,一上来就一行一行读可能会觉得难以读懂甚至放弃,这个时候不妨试试==以debug的形式打开==,一步一步来理解源码的运行机制,因此本文将主要使用Debug的形式对java.util.ArrayList的源码进行分析
首先打开设置,进行下图操作,在Debugger中找到Stepping,然后取消勾选Don’t step into the classes,这一步是为了在debug过程中可以步入到ArrayList源码中。
2.ArrayList关系图
右键ArrayList找到diagram,就可以得到关系图,如图所示,从上往下依次分析
2.1 Iterable
Collection集成了Iterable接口,那么ArrayList只要实现Iterator就可以使用foreach进行循环迭代,下面就是ArrayList中的迭代器实现
1 | private class Itr implements Iterator<E> { |
使用举例:
1 | public static void main(String[] args){ |
2.2 Cloneable
实现了该接口并实现Object.clone()方法就可以使用克隆(复制)方法,从下面的源码可以看到,在ArrayList中的克隆方法是对当前ArrayList中所有元素的一个复制
1 | public Object clone() { |
2.3 RandomAccess
随机访问,这个接口是声明ArrayList是具备随机访问的特性,也就是说==通过索引访问元素的时间复杂度为常数==,这是由于ArrayList是数组实现的,只要通过a[i]就可以在常数时间内索引到对应的元素,而LinkedList这种使用链表实现的,需要从头开始遍历,时间复杂度为O(n),自然LinkedList就不是随机访问的。(有无机制区别很大,将会在LinkedList源码中进行详细分析)
2.4 Serializable
序列化,这个是为了保存当前ArrayList对象的,通过输入输出流的形式对ArrayList对象进行读写操作
2.5 AbstractList
抽象类是可以有具体实现方法的,官方说他的存在是为了扩展一些方法从而减轻ArrayList的工作
3. ArrayList
一般而言,拿到源码可以先看常量
3.1 常量
1 | // 默认ArrayList的容量为10 |
3.2 以debug的形式理解ArrayList
现在开始使用debug进行一些分析,先写一个add方法我们进去看一下,然后再main函数出打一个断点,开始debug,以下只做一些重点部分说明(比如调用父类构造函数就不展示了)。
1.首先,由于我们new的时候没有传入参数,因此调用ArrayList的无参构造,默认是赋值ArrayList一个空值
2.自动装箱,因为我们add(2),将int类型的2装箱成Integer对象
3.现在进入到add方法里面,但是此时的ArrayList是空的,因此ensureCapacityInternal就是来扩展ArrayList的大小的,初始size是0,这里传入size+1为参数,表明ArrayList的容量至少要为size+1=1
4.进入ensureCapacityInternal,发现其调用了ensureExplicitCapacity,参数有一个calculateCapacity方法,第二张图是这个方法的实现,他会取size+1和默认值10两者中的最大的值,显然这里直接取10,然后10就传给了minCapacity
- 所以10就传入到ensureExplicitCapacity中,初始空间elementData,length是0,判断成立,执行执行grow函数,顾名思义,grow应该是用于扩展ArrayList大小的
6.然后就进入到grow函数,oldCapacity >> 1表示右移一位(移位运算符是在二进制上进行操作,右移一位表示原数除以2,可以联想十进制100右移一位就变成了10,也就是除以10),newCapacity = oldCapacity + (oldCapacity >> 1)也就是容量变成原来的1.5倍,然后下面的代码就是判断一下传进来的minCapacity和1.5倍oldCapacity谁大一些,就用大的,而hugeCapacity(minCapacity)就是虚拟机允许的最大容量
7.进入到copyOf方法,很容易发现这个方法就是重新new一个扩容的数组,然后把原来数组中的元素一个一个搬到新数组中,完成数组动态增长
8.至此就完成了所有的工作,其实这已经将ArrayList的核心搞懂了,像其他的一些方法,get、set、addAll就都很简单了,读者可以尝试自己去试试
3.3 快速失败机制(fail-fast)
最难最核心的东西基本上都体现出来了,现在讲讲自动失败机制(fail-fast)
这个机制其实是为了安全,比如当我使用Iterator遍历ArrayList的时候,对ArrayList进行修改怎么办,比如使用ArrayList.remove方法把我即将要遍历读取的元素给删了,这个时候我遍历出来的数组就不是原来的数组了(下面是单线程下的一个示例,多线程更容易出现该问题,并且更隐蔽)
1 | public static void main(String[] args) { |
这个时候会报错
那怎么实现这个机制呢,那就是用一个标志modCount,每当ArrayList做一次add、remove的时候就会让modCount加一,迭代器里面有一个expectedModCount,然后当你进行Iterator迭代的时候,会执行 expectedModCount = modCount,开始遍历后,如果修改了ArrayList,ModCount仍然会增加,而expectedModCount只有一次赋值,所以这两个的值将会不相等,只要不相等就会抛出并发修改异常