ArrayList源码解析

基础概念

【1】ArrayList是动态增长或者缩减的索引序列,底层基于数组实现,但是会自动进行扩容。即动态数组

【2】继承关系:

  • public class ArrayList<T> extends AbstractList<T> 
                              implements List<T>, RandomAccess, Cloneable, Serializable<T>{}
    
  • 继承类和实现的接口如下

    • RandomAccess接口:标识接口,标识实现该接口的类支持快速随机访问
    • Serializable接口:标识接口,标识该类可以被序列化,即开启序列化标识
    • Cloneable接口:标识接口,实现该接口后,然后在类之中重写Object的clone方法,然后调用clone方法才能克隆成功,Cloneable接口是合法调用clone方法的标识,注意深克隆和浅克隆
    • List接口和抽象类AbstractList抽象类定义了需要实现的方法和对通用方法做实现

源码分析

【1】成员变量

  • private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    private int size;
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
  • 成员变量的含义解析:

    • DEFAULT_CAPACITY:定义缺省容量值,即默认的初始化常量
    • EMPTY_ELEMENTDATA:ArrayList空实例共享的一个空数组
    • DEFAULTCAPACITY_EMPTY_ELEMENTDATA:用于默认大小的空实例的共享数组实例。将EMPTY_ELEMENTDATA和this(DEFAULTCAPACITY_EMPTY_ELEMENTDATA);区分开来,以便在添加第一个元素的时候的得到扩容量
      • 详细区别:即有参构造方法且如果创建的长度为0的话,那么elementData = EMPTY_ELEMENTDATA
      • 无参构造方法创建ArrayList的时候,elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA
      • 这样做的原因是为了计算后续的扩容问题
    • size:elementData之中存放元素的个数
    • elementData:底层存放元素的Object类型数组。此变量用了transient修饰,表示被序列化时会忽略
    • MAX_ARRAY_SIZE:指定数组的最大长度,因为通过索引访问,索引为int类型

【2】构造方法

  • public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
  • 无参构造方法默认创建的空数组为成员变量之中的DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  • public ArrayList(int initialCapacity) {
        if(initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if(initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("");
        }
    }
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if((size == elementData.length) != 0) {
            if(elementData.getClass != Object[].class) {
                elementData = Arrays.copyOf(elementData, size, Object[].class);
            }
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    
  • 有参构造方法如果长度为0那么创建的空数组为成员变量之中的EMPTY_ELEMENTDATA

  • 注意点:

    • 参数为集合类型,长度非0的时候进行了类型判断。原因:集合转换为数组的时候,不一定会转换为Obejct类型即elementData.getClass != Object[].class。如果通过Arrays.asList等等方式创建出来的集合。利用Arrays的toArray方法转换为数组的时候会保留原本的类型。elementData.getClass!=Object[].class的返回值为true。所以需要对这种情况进行处理。通过Arrays.copyOf方法将其转换为Object类型的数组

【3】add添加系列方法源码

添加元素数组扩容方法解析

【1】扩容算法源码

  • public boolean add(E e) {
        ensureCapacityInternal(size + 1); // 当前数组元素长度加1得到添加成功时的需要的长度,即最小扩容长度
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 如果通过无参构造方法创建ArrayList
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 扩容到默认长度10
        }
        ensureExplicitCapacity(minCapacity); 
    }
    private void ensureExplicitCapacity(int minCapacity) {
        if(minCapacity - elementData.length > 0) { // 如果当前数组长度比最小扩容长度要小,那么进行扩容
            grow(minCapacity);
        }
    }
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length(); // 原来数组的长度
        int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容到原来的1.5倍,通过位运算实现
        if(newCapacity - oldCapacity < 0) { // 如果初始长度为0,导致位运算后扩容后长度newCapacity仍然为0
            newCapacity = minCapacity; // 将最少扩容长度赋值给扩容后的长度
        }
        if(newCapacity - MAX_ARRAY_SIZE > 0) { // 索引为int类型,避免扩容后长度超过int的上界
            newCapacity = hugeCapacity(minCapacity);
        }
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    private static int hugeCapacity(int minCapacity) {
        if(minCapacity < 0) {
            throw new OutofMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)?Integer.MAX_VALUE:MAX_ARRAY_SIZE;
    }
    

【2】数组扩容方法流程解析:

  • a.通过集合长度变量[size+(添加的数量)]获取到数据需要的最小容量
  • b.判断是否通过无参构造方法创建的空数组,如果是那么首次直接扩容到默认长度DEFAULT_CAPACITY
  • c.如果不是,那么用最小扩容长度-去数组的长度。如果最小扩容长度大于数组长度,那么进行扩容
  • d.扩容运算通过位运算实现。即扩容到原来的1.5倍
    • 扩容算法:原长度 + 原长度右移1位 ===> 原长度 + 原长度/2
    • 可能原来的长度为0,位运算之后,长度仍然为0,此时将最小扩容长度赋值给新长度
    • 可能扩容之后长度超过int的界限,边界处理,最大长度为int的上限值Integer.MAX_VALUE
  • e.扩容之后需要保证,扩容后的长度大于原数组的长度,且不超过int的上限
  • f.扩容之后,数组元素拷贝通过System.arrayCopy方法实现
  • g.然后进行添加
添加元素算法解析

【1】源码

  • public boolean add(E e) {
        ensureCapacityInternal(size + 1); // 当前数组元素长度加1得到添加成功时的需要的长度,即最小扩容长度
        elementData[size++] = e;
        return true;
    }
    public void add(int index, E element) {
        rangeCheckForAdd(index); // 检查索引是否越界
        ensureCapacityInternal(size + 1); // 判断数组长度是否足够,不够则进行扩容
        System.arraycopy(elementData, index, elementData, index + 1, size - index); // 移动元素
        elementData[index] = element; // 添加
        size++; // 维护数组长度
    }
    private void rangeCheckForAdd(int index) {
        if(index < 0 || index > size) {
            throw new IndexOutOfBoundsException();
        }        
    }
    
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray(); // 转换为数组
        int numNew = a.length; // 获取长度,后续的拷贝元素方法需要使用
        ensureCapacityInternal(size + numNew); // 扩容
        System.arraycopy(a, 0, elementData, size, numNew); // 拷贝元素到原集合后面
        size += numNew; // 维护长度
        return numNew != 0; // 通过长度判断是否追加成功
    }
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);
        int numMoved = size - index;
        if(numMoved > 0) {
            System.arraycopy(elememtData, index, elementData, index + numNew, numMoved);
        }
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
    

【2】思路和索引插入单个相同,扩容加上移动元素。核心是通过System.arraycopy方法实现移动数组元素。addAll的插入写的很秀。非常厉害。一次扩容两次拷贝,第一个拷贝实现插入位置后面元素的移位,第二次拷贝直接覆盖需要插入的位置元素

【4】删除系列方法源码解析

【1】删除算法实现

  • public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData[index]; // 获取要删除位置的元素
        int numMoved = size - index - 1; // 获取删除位置之后有几个元素,即获取拷贝数量
        if(numMoved > 0) {
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        }
        elementData[--size] = null;
        return oldMoved;
    }
    
    /**
     * 关于System.arraycopy方法,ArrayList底层数组操作大量使用了这个方法完成数据拷贝到新数组之中
     * 数组一旦创建长度便不可更改,本质上每次都是创建一个新数组,然后拷贝元素
     */
    Syste.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
    /**
     * src: 源数组
     * srcPos: 源数组的起始位置
     * dest: 目标数组
     * destPos: 目标数组的起始位置
     * length:拷贝元素的数量
     */
    public boolean remove(Object o) {
        if(o == null) { // 如果删除元素为null,遍历找到值为null的索引位置,进行删除
            for(int index = 0; index < size; index++) {
                if(elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
            }
        } else {
            for(int index = 0; index < size; index++) {
                if(o.equals(elementData[index]) {
                    fastRemove(index);
                    return true;
                }
            }
        }
        return fasle;
    }
    // 和删除单个元素的算法一样(快速删除,不检查索引越界问题)
    public void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if(numMoved > 0) {
            System.arraycopy(elementData, index, elementData, index + 1, numMoved);
        }
        elementData[--size] = null;
    }
                   
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
    public boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0; // r作为循环次数的记录,w为保留元素的下标
        boolean modified = false; // 默认执行未修改操作
        try {
            for(; r < size; r++) { // 遍历
                if(c.contains(elementData[r]) == complement) { //如果不存在
                    elementData[w++] = elementData[r]; //保存该元素
                }
            }
        } finally {
            if(r != size) { //循环中断的处理,后面未遍历的元素默认保留,拷贝到elementData要保留的最后一个元素后面
                System.arraycopy(elementData, r, elementData, w, size - r);
                w = w + size - r;
            }
            if(w != size) { //elementData保留个数不等于实际数组大小
                for(int i = w; i < size; i++) {
                    elementData[i] = null;
                }
                modCount = modCount + size - w;
                modified = true;
            }
        }
        return modified;
    }
    

[2]方法解析

  • 单个删除其实非常简单,找到删除位置,后面的位置依次前移一个位置,覆盖。最后置空最后一个索引位置。

  • 批量删除removeAll的核心代码在于batchRemove的try块的for循环这里

     for(; r < size; r++) { // 遍历
         if(c.contains(elementData[r]) == complement) { //如果不存在
             elementData[w++] = elementData[r]; //保存该元素
             }
    }
    
    • 解析:删除的时候complement为false,表示如果elementData[r]如果不在集合之类,那么把这个元素保留到elementData[w]位置上,重置到数组本身
    • 如果存在,那么不执行保存操作
    • 批量删除实际上是求差集,即complement为false时求的是差集,如果为true则是求并集
    • r!=size:表示判断r是否会等于size,如果批量删除操作成功,那么表示elementData遍历完毕,此时r的值一定等于size的长度。如果r!=size,那么表明出现异常导致数组没有遍历完毕,此时r(即遍历位置)把剩下没遍历完的当作不需要删除的数组元素,放到保留的数组元素的数组后面。
    • r=size:继续执行判断w!=size,原数组elementData已经遍历完,w不等于size表示集合c存在要删除的数组元素 则从w开始遍历,在w往后的元素都置空elementData[i] = null,此时数组保存的就是批量删除之后的结果

【5】其余源码解析

  • 获取元素
  • public E get(int index) {
        rangeCheck(index); // 检查索引是否越界
        return elementData[index]; // 如果索引没有越界,那么直接返回index索引位置的元素
    }
    
  • 设置元素set
  • public E set(int index, E element) {
        rangeCheck(index); // 检查索引是否越界
        E oldValue = elementData[index]; // 返回原来该索引位置的元素
        elementData[index] = element; // 重新设置该索引位置元素的值
        return oldValue; // 返回原来的值
    }
    
  • 置空集合clear
  • public void clear() {
        for(int i = 0; i < size; i++) {
            elementData[i] = null; // 遍历集合,将集合每一个元素都置为null
        }
        size = 0; // 将长度置为0
    }
    
  • 获取长度size
  • public int size() {
        return size; // 返回长度变量size
    }
    
  • 判断集合是否为空isEmpty
  • public boolean isEmpty() {
        return size == 0; // 通过长度进行判断,如果长度为0,那么则为空
    }
    
  • 判断是否包含指定的元素contains
  • public boolean contains(Object o) {
        return indexOf(o) >= 0; // 查询指定元素,如果返回的索引位置大于等于0那么则存在该元素
    }
    public int indexOf(Object o) {
        if(o == null) { // 如果o为null
            for(int i = 0; i < size; i++) { // 遍历如果有元素为null,返回索引位置 
                if(elementData[i] == null) {
                    return i;
                }
            }
        } else {
            for(int i = 0; i < size; i++) {
                if(o.equals(elementData[i])) {
                    return i;
                }
            }
        }
        return -1; // 如果不存在则返回-1
    }
    
  • 从后向前判断是否存在该元素lastIndexOf
  • public int lastIndexOf(Object o) {
        if(o == null) {
            for(int i = size - 1; i > =0; i--) {
                if(elementData[i] == null) {
                    return i;
                }
            }
        } else {
            for(int i = size - 1; i > =0; i--) {
                if(o.equals(elementData[i])) {
                    return i;
                }
            }
        }
        return -1;
    }
    
  • 转换为数组toArray
  • public Object[] toArray() {
        return Arrays.capyOf(elementData, size);
    }
    // 最底层仍然是通过调用System.arraycopy方法实现,继续看底层实现,以下是Arrays类之中
    public static <T> copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        // 创建数组,长度和传入的original类型相同,长度相同
        T[] copy = ((Object)newType == (Object)Object.class) ?
            (T[]) new Object[newLength] : Array.newInstance(newType.getComponenetType(), newLenght);
        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
        return copy;
    }
    
  • ((Object)newType == (Object)Object.class):判断newType是不是Object类型的数组。将其强转为Object类型的原因是:要利用==进行判断内存地址,从而判断它们是不是同一种类型。因此需要向上强转为Object,否则编译错误

  • newType.getComponentType():方法作用是返回数组内元素的类型,不是数组的时候返回null

  • Array.newInstance(newType.getComponenetType(), newLenght):创建一个类型和newType类型一样,长度为newLength的数组。Array.newInstance调用本地方法Array.newArray

  • Array.newInstance返回为Object实际上是数组

  • (T[])new Object[newLength];此处可以强转的原因是:如果传入的数组是Object类型,那么三元表达式一定为true此时,创建Object数组,T也是Object类型,毫无问题。如果条件为false,那么获取到元素类型,通过反射创建数组,T的类型就是传入数组的类型,通过反射也解决了类型转换的问题,即通过反射创建出和原数组类型相同的数组

ArrayList的线程安全问题

ArrayList为什么线程不安全

以添加方法为例子
  • 添加方法源码如下

  • public void add(E e) {
        ensureCapacityInternal(size + 1); // 当前数组元素长度加1得到添加成功时的需要的长度,即最小扩容长度
        elementData[size++] = e;
        return true;
    }
    
  • 线程不安全地方代码就是elementData[size++] = e;此行代码严格来说执行步骤为:

    • elementData[size] = e; 设置size索引处的值
    • size++; 然后长度自增
  • 因为elementData[size++] = e; 设置和长度增加是非原子性操作。如果多线程同时添加操作的时候,那么则会导致线程安全问题。