集合对象-ArrayList实现原理源码分析

Posted hymKing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了集合对象-ArrayList实现原理源码分析相关的知识,希望对你有一定的参考价值。

最近再重看并发编程,又有新的体会,读书真是一个神奇的事情,读书能够提升认知水平,认知水平提高后,再复读往往能够获得新知。扯远了,在读构建线程安全类章节中,看到了CopyOnWriteArrayList,今天咱们结合ArrayList,Vector做一个简单的对比记录。

主要从以下几个维度来看他们之间的联系和差别,比如代码底层的数据结构的实现、扩容机制上的区别、是否线程安全、性能上的差别。

一、ArrayList

继承体系图:

ArrayList直接实现了了四个接口如上,其中List提供了基础的添加、删除、遍历操作;

RandomAccess提供了随机访问能力、Cloneable接口,提供了可以被克隆、Serializable接口,提供了可以序列化的能力;

RandomAccess、Cloneable、Serializable都是空接口,这种空接口在Jdk的机制中,这种标记接口被实现后,起到给类打标记的作用,程序在运行期间通过识别标记,实现相应的功能。当然有兴趣了解更多标记接口的实现和运行机制,可以查阅相关其它资料。

二、源码分析

内部存储结构声明部分:

/**
 * 默认容量为10,通过new ArrayList()创建时的默认容量
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 空数组,用于传入的容量为0的时候使用,是通过new ArrayList(0)创建的时候使用这个数组
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 空数组,通过new ArrayList()时,或传入容量时使用,添加第一个元素的时候会重新初始化为默认容量大小
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 存储元素的数组,transient是序列化排除作用,这个字段不会被序列化
 */
transient Object[] elementData; // non-private to simplify nested class access

/**
 * 包含元素个数,即list的大小,注意非存储元素数组的大小
 * @serial
 */
private int size;

上面定义了三种不同的数组,都是怎么用的呢,接着看构造函数方法源码

/**
 * 依据初始化容量构造一个空列表
 *
 * @param  initialCapacity  list的初始化容量
 * @throws IllegalArgumentException 容量为负数的时候会
 */
  public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * 构造一个默认容量的空列表
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

从构造函数中,可以看看出各个成员变量的空数组在构造过程中的使用。接下来看一下,添加元素

 public boolean add(E e) {
        //检查是否要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
//检查是否要扩容的过程,检查当前容量,确定明确的符合本次添加调用的所规划容量值
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
      //通过new ArrayList()方式创建,元素素组elementData指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA
      //计算最小容量
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
      //确定明确的容量
    ensureExplicitCapacity(minCapacity);
}

确定最小容量是通过这句代码实现:minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

其中DEFAULT_CAPACITY=10,第一次添加元素size=0,minCapacity=size+1=1,取最大值。那么通过new ArrayList()默认构造方法创建的Arraylist,元素数组会在被第一次添加元素的时候,初始化默认长度为10的数组。接着看确定明确容量的源码细节,ensureExplicitCapacity(minCapacity)的源码如下:

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // 容量溢出扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

如果最小容量超出了计算出来的最小容量minCapacity,则执行扩容,调用grow(minCapacity)方法,方法源码如下:

//为支撑最小容量,进行扩容,并不一定扩容到最小容量相等的容量值
private void grow(int minCapacity) {
    // overflow-conscious code
    //旧的容量值
    int oldCapacity = elementData.length;
    //新的容量值 oldCapacity >> 1 右移运算符 相当于除以2
    int newCapacity = oldCapacity + (oldCapacity >> 1);//1.5倍
    //扩容后不满足最小容量值,确定新的容量值为最小容量值minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果新容量已经超过最大容量了,则使用最大容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);//方法内部的实现,比较简单就不多注解了
    //以新容量拷贝出来一个新数组,并指向原元素存储数组elementData
    elementData = Arrays.copyOf(elementData, newCapacity);
}

至此,arrayList的核心实现,就分析完了,其它相关方法包括 addAll、remove等等其它实现,大部分操作的最终也都调用了原始数组的copy,这里就不详述。

关于arrayList的线程安全性,我简单看一下方法声明部分的代码如下:

public void add(int index, E element){}
public E remove(int index) {}
public E get(int index)

整个arrayList的操作,没有加入任何线程安全维度的设计,arrayList是非线程安全的动态数组集合类。简单再看下remove方法的源码:

/**
 * 按索引位置移除元素
 */
public E remove(int index) {
  //越界检查
  rangeCheck(index);
  .......
}
/**
 * 移除元素
 * @param o element to be removed from this list, if present
 * @return <tt>true</tt> if this list contained the specified element
 */
public boolean remove(Object o) {
    //区分null和非null元素
    if (o == 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 false;
}

fastRemove(index)的源代码:

/*
 * 跳过边界检查,移除的元素不返回
 */
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
      //代码中的大部分操作都使用了数组复制,调用的是native层的方法,效率会高
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

总结一下

  • arrayList的实现的数据结构很简单,就是数组;
  • arrayList的设计一套扩容机制,通用情况下的扩容量是原数组大小的1.5倍
  • arrayList是非线程安全的,并发编程不建议使用。
  • arrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);
  • arrayList添加或删除元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);
  • arrayList支持集合并集、交集、差集操作。

落笔写的初衷是做几个集合对象的对比,ArrayList的内容篇幅就够长了,再分析下去篇幅就更长了,后续再补充集合对象的对比总结。

以上是关于集合对象-ArrayList实现原理源码分析的主要内容,如果未能解决你的问题,请参考以下文章

集合对象-ArrayList实现原理源码分析

JAVA——底层源码阅读——集合ArrayList的实现底层源码分析

Java集合框架 List接口实现类--ArrayList源码分析

ArrayList源码阅读分析(JDK1.8)

Java中Arraylist源码分析

java集合之ArrayList源码分析