ArrayList源码分析

本文以Java8为例java

从ArrayList实现的接口提及

查看ArrayList源码会发现ArrayList继承了AbstractList类,实现了List, RandomAccess, Cloneable, java.io.Serializable接口。

AbstractList

AbstractList是抽象类,里面有众多List基本方法的基本实现。事实上JDK中有众多相似AbstractXXX的类,他的思想是就是把众多公共的实现提取出来,AbstractXXX面向的是工具类的开发者,咱们能够基于AbstractList来实现本身的List。而ArrayList只是JDK已经帮咱们实现好的,面向工具类的使用者。数组

List

很少说,多态。父类的引用指向子类的实例。安全

RandomAccess

RandomAccess即随机访问。bash

先给出结论:集合中元素的访问分为随机访问和顺序访问。 随机访问相似于数组下标的访问,顺序访问相似于链表的访问,随机访问直接经过下标取值性能较好,顺序访问以迭代器遍历性能比较好。 查看RandomAccess接口,仅仅是一个空接口而已。app

查看调用RandomAccess接口的类,发现除了实现RandomAccess,其余都是用于instaceof判断。

原来,RandomAccess用意仅仅是一个标记而已,即标记接口,用于判断List是否为RandomAccess的实现。以Collections的binarySearch方法为例:dom

二分查找先判断list是不是RandomAccess的子类或大小是否小于阈值5000,若是为真则会执行indexedBinarySearch

会发现indexedBinarySearch是以数组下标的方式获取值。若是为假会执行iteratorBinarySearch

iteratorBinarySearch是以迭代器的方式获取值。即JDK以性能方面的考虑,若是list支持随机访问或size比较小,则如下标索引的方式获取内容,不然以迭代器的遍历获取内容。

因此RandomAccess仅仅是一个类的标记而已。函数

Cloneable

Cloneable是否可克隆,查看Cloneable接口 工具

查看调用Cloneable接口的类,都是实现Cloneable接口。与RandomAccess相似,Cloneable接口也是标记接口,用于判断该类的对象是否可克隆。

这里会有一个疑问:什么是可克隆?源码分析

先给出结论:clone方法是基类Object的方法,只有实现了Cloneable接口的类才可使用clone方法,不然会抛CloneNotSupportedException异常。下面是某版本JDK的clone方法的源码,会发现若是不是Cloneable的子类则会抛异常。性能

protected Object clone() throws CloneNotSupportedException {
    if (!(this instanceof Cloneable)) {
        throw new CloneNotSupportedException("Class " + getClass().getName() +
                                             " doesn't implement Cloneable");
    }

    return internalClone();
}

/*
 * Native helper method for cloning.
 */
private native Object internalClone();
复制代码

然而查看JDK1.8的clone方法,仅仅是一个本地方法而已,他又是怎么作到抛异常的呢?

protected native Object clone() throws CloneNotSupportedException;
复制代码

原来在JDK1.8中,若是咱们查看JVM的C++源码,会发现“对象是否实现了cloneable接口抛异常”的动做实如今了JVM层中。

因此ArrayList经过实现了cloneable接口,支持clone方法。

另外,有说法认为Java的类经过实现Cloneable来标识出对象是否可使用clone是一种很糟糕的设计,未实现Cloneable确对外暴露clone方法的规则很奇怪。按照如今的视角来看,若是给类从新设计下“是否可克隆”这个规则,该怎么实现好呢?这里存疑。

java.io.Serializable

一样也是标记接口,表示该类的对象是否能够序列化。

成员属性

// 若初始化时未指定容量大小,默认为10
private static final int DEFAULT_CAPACITY = 10;

// 一个空数组而已
private static final Object[] EMPTY_ELEMENTDATA = {};

// 也是一个空数组,只不过与EMPTY_ELEMENTDATA作区分。
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA表示初始化是无参构造
// 而EMPTY_ELEMENTDATA是有参构造,只不过参数是0
// 之因此用来区分是由于无参构造是默认容量为10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存储对象的容器
transient Object[] elementData;

// 存储对象的多少
private int size;

复制代码

主要方法

构造方法

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 ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
复制代码

三个构造方法参数分别是容量、空、集合。经过观察代码会发现容量为0时, this.elementData有两种状况

构造函数传入initialCapacity时this.elementData = EMPTY_ELEMENTDATA;

无参构造函数时this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

关于这两个属性,源码给的解释是

/**
 * Shared empty array instance used for empty instances.
 * 用于空实例的共享空数组实例
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 * 共享空数组实例,用于默认大小的空实例。
 * 咱们将其与EMPTY_ELEMENTDATA区分开来,以了解添加第一个元素时应该膨胀多少
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
复制代码

DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA其实就是实例化ArrayList方式的区分。

DEFAULTCAPACITY_EMPTY_ELEMENTDATA在后面add方法中会详细解释,其实这里能够理解为DEFAULTCAPACITY_EMPTY_ELEMENTDATA表示实例化时是无参构造,未指定容量,在调用add方法时这种状况会默认此刻容量为10。

而EMPTY_ELEMENTDATA表示在咱们实例化对象时指定了容量就是0。

add方法

public boolean add(E e) {
    // 内部确保容量
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}


public void add(int index, E element) {
    rangeCheckForAdd(index);
    // 内部确保容量
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
            size - index);
    elementData[index] = element;
    size++;
}
复制代码

add方法的核心ensureCapacityInternal,内部确保容量。

内部确保容量即add元素时,程序里经过当前的size自动判断当前是否须要扩容。与其相对应的是显示确保容量。ensureCapacityInternal源码分析以下

// minCapacity表示容器要确保支持的容量
// elementData的长度至少要大于等于minCapacity,是需求的最低大小
// 好比add时 minCapacity就是size+1
// ensureCapacity调用ensureCapacity显示扩容时,minCapacity就是传入的参数
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 该方法仅仅是多作了一步判断,若是是以new ArrayList()无参构造函数实例化的对象,且是第一次add元素,则默认的最小容量为10。
// 若是没有默认容量,由于容器最初容量小,add元素时1.5倍的扩容方式会形成频繁的扩容。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

// 显示确保容量,在ensureCapacityInternal中调用只是方法的公用
// “显示”的含义主要体如今ensureCapacity方法的调用,ensureCapacity是人为的显示扩大容量
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

// 实际扩容,默认按1.5被扩容,若是1.5倍仍是小于minCapacity,则以minCapacity为准
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 容量扩容为原有的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 若是扩容后仍是小于最小容量,以最小容量为准
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
复制代码

若是未指定容量,add方法以10为初始值,每次扩容以1.5倍扩容。因此若是有大量数据add,也会触发屡次扩容。而每次扩容其实是将数组内容复制到新的数组里。

trimToSize

精简化容器。ArrayList在实际使用中,elementData的长度大于等于size,而trimToSize会将elementData按照size从新复制一份给elementData,最小化ArrayList实例的存储。若是实例内容不变,能够调用该方法节约内存。

public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
                ? EMPTY_ELEMENTDATA
                : Arrays.copyOf(elementData, size);
    }
}
复制代码

ensureCapacity

至关于扩容,也是咱们前面提到的显示确保容量。

给定一个minCapacity,保证ArrayList能够容纳至少minCapacity数量的对象。

public void ensureCapacity(int minCapacity) {
    // 以此来判断咱们实例化时是否为 new ArrayList()
    // 若是是,他的默认长度在add时就是10,不须要扩容
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            ? 0
            : DEFAULT_CAPACITY;
    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}
复制代码

clone

前面已经作过度析,经过实现Cloneable接口,使得clone方法可用。重写clone方法,复制出一份数组,但其自己仍是浅拷贝,数组里引用的对象没有被复制。

modCount表示改动次数,新克隆的对象置为0。

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); } } 复制代码

remove

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; // clear to let GC do its work
    return oldValue;
}

public boolean remove(Object o) {
    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;
}

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                numMoved);
    elementData[--size] = null; // clear to let GC do its work
}
复制代码

remove其实就是将index后面的元素向前移动一位。因此若ArrayList很大,不适合使用remove方法。

与Vector对比

Vector就是线程安全版的ArrayList,它的实现与ArrayList基本相似,这里说下比较明显的3点区别

Vector线程安全

Vector针对容器的操做都加上了synchronized关键字来保证线程安全,譬如:

public synchronized void ensureCapacity(int minCapacity) {
    if (minCapacity > 0) {
        modCount++;
        ensureCapacityHelper(minCapacity);
    }
}
复制代码

构造方法略有不一样

public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
} 
复制代码

相比ArrayList,Vector多了一个构造方法,多传入一个参数capacityIncrement,能够用来指定每次扩容的增量。

另外new Vector()默认容量也是10,只不过在实例化时数组的长度就已是10了。而ArrayList中是add第一个元素以后数组的长度才会变成10。

public Vector() {
    this(10);// 默认容量为10
}

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}
复制代码

扩容规则略有不一样

Vector的扩容方法

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 若是有capacityIncrement,按照capacityIncrement扩容,不然按照当前容量的一倍扩容
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
            capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}
复制代码

Vector的扩容默认是按一倍扩容,若是指定capacityIncrement,则按照capacityIncrement扩容。

然而就我而言,对Vector应用几乎没有应用到。若是须要线程安全的List通常使用的是CopyOnWriteArrayList。

与LinkedList对比

LinkedList就是个双向链表结构,每一个节点维护其前置节点和后驱节点。

LinkedList的源码比较简单就不作分析了。

ArrayList是线性结构支持RandomAccess,LinkedList是链式结构。

总结

  • 实现RadomAccess随机访问,使得ArrayList在一些方法中是以数组下标取值。

  • 实现CloneAble支持克隆,浅拷贝。

  • 默认容量为10,以1.5倍递增的方式扩容。

  • 数据量大时并不适合remove。

相关文章
相关标签/搜索