ArrayList源码详细分析——jdk1.8

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中的迭代器实现

 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();
         }
     }

使用举例:

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中所有元素的一个复制

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 常量

// 默认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

在这里插入图片描述
在这里插入图片描述
5. 所以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方法把我即将要遍历读取的元素给删了,这个时候我遍历出来的数组就不是原来的数组了(下面是单线程下的一个示例,多线程更容易出现该问题,并且更隐蔽)

 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只有一次赋值,所以这两个的值将会不相等,只要不相等就会抛出并发修改异常

4总结

1.ArrayList继承了RandomAccess,由于ArrayList本质是数组,所以查询元素很快,时间复杂度为常数,但是插入删除元素需要逐个挪动其他元素,时间复杂度为O(n),因此查询工作量大的时候用ArrayList比较好,需要反复插入删除元素使用LinkedList比较好
2.ArrayList默认初始容量为10,如果容量不够,则新建一个容量为原来1.5倍的数组,然后将旧数组的元素逐一复制到新数组中,这就完成了grow操作
3.ArrayList具有快速失败机制fail-fast,在使用迭代器遍历ArrayList的时候,如果外界对ArrayList进行了修改,则会抛出并发修改异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值