avatar

ArrayList源码分析-jdk1.8

简介:

源码一般由大量代码组成,一上来就一行一行读可能会觉得难以读懂甚至放弃,这个时候不妨试试以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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private class Itr implements Iterator<E> {
int cursor; // 下一个元素的索引
int lastRet = -1; // 最后一个元素的索引; 没有就是-1
int expectedModCount = modCount;// 这个是java的快速失败机制,后文讲

Itr() {}

public boolean hasNext() {
// 检查ArrayList中有没有下一个元素,size是ArrayList中保存的元素个数,只要下一个索引cursor不等于元素个数,那肯定就有元素了
return cursor != size;
}

@SuppressWarnings("unchecked")
public E next() {
checkForComodification(); // 快速失败机制
int i = cursor;
// 超出实际大小size的元素是空元素
if (i >= size)
throw new NoSuchElementException();
// 本类Itr()是一个内部类,调用外部类的字段要用ArrayList.this.的形式
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
// 取出下一个元素的值
return (E) elementData[lastRet = i];
}

public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();

try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; // remove不使用快速失败机制
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

使用举例:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args){
ArrayList<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");

for (String s : list) {
System.out.println("element : " + s);
}
}

2.2 Cloneable

实现了该接口并实现Object.clone()方法就可以使用克隆(复制)方法,从下面的源码可以看到,在ArrayList中的克隆方法是对当前ArrayList中所有元素的一个复制

1
2
3
4
5
6
7
8
9
10
11
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}

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
2
3
4
5
6
7
8
9
// 默认ArrayList的容量为10
private static final int DEFAULT_CAPACITY = 10;
// 指定ArrayList为空
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// transient是保护机制,序列化的时候不会被写入到流中,elementData就是一个对象数组了
transient Object[] elementData;
// ArrayList中存放的元素个数,注意这里和ArrayList的容量区分开
private int size;

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

在这里插入图片描述
在这里插入图片描述

  1. 所以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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0 ; i < 10 ; i++ ) {
list.add(i + "");
}
Iterator<String> iterator = list.iterator();
int i = 0 ;
while(iterator.hasNext()) {
if (i == 3) {
list.remove(3);
}
System.out.println(iterator.next());
i ++;
}
}

这个时候会报错
在这里插入图片描述
那怎么实现这个机制呢,那就是用一个标志modCount,每当ArrayList做一次add、remove的时候就会让modCount加一,迭代器里面有一个expectedModCount,然后当你进行Iterator迭代的时候,会执行 expectedModCount = modCount,开始遍历后,如果修改了ArrayList,ModCount仍然会增加,而expectedModCount只有一次赋值,所以这两个的值将会不相等,只要不相等就会抛出并发修改异常

文章作者: Dxwell
文章链接: https://dxwell886.github.io/2021/02/10/ArrayList%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-jdk1-8/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Dxwell的博客
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论