Java 集合 1:ArrayList 源码分析

ArrayList 源码分析

全篇以 Java8 为基础

Java文档

Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)

List 接口的可变数组实现,实现可 List 接口的全部方法,允许包括控制在内的所有数据元素。除此之外,还提供了操作内部数组大小的方法。与 Vector 类似,只不过 ArrayList 不是线程安全的。

​ ArrayList 类的声明如下所示,它实现了 List 接口,属于 Java Collections Framework 。此外,还是实现了 RandomAccess 、 Cloneable 、 Serializable 接口,说明它支持随机访问、可克隆、可序列化。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

关键属性

​ ArrayList 类的属性如下所示,其中最主要的就是 elementData ,它就是用来存储放到 ArrayList中的数据。

/**
 * 序列化相关
 */
private static final long serialVersionUID = 8683452581122892189L;

/**
 * 默认容量
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 为减少开销而设置的空数组
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 同上,为默认容量(10)下的空数组,同时在添加第一个元素时与上面的空数组作区分
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 存储数据的数组
 * ArrayList 的 capacity 即 elementData 的长度
 * 满足 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 条件的对象在第一次添加数据时会被扩展到默认容量(10)
 */
transient Object[] elementData; // non-private to simplify nested class access

/**
 * ArrayList 内数据的数量,有 capacity >= size
 */
private int size;

/**
 * 最大容量
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

构造函数

​ ArrayList 有三个构造函数,如下所示,可以看到再三个方法中都有使用到 EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA 。在原本的 Java7 中,这三个构造函数在初始化容量为 0 时都会创建一个长度为 0elementData ,这样就会增加系统的开销,使用这两个静态的属性,就可以减小系统的开销并在第一次插入数据时才会进行数组分配。

/**
 * 创建指定容量的 ArrayList
 * 0 则使用 EMPTY_ELEMENTDATA
 * < 0 抛出异常
 */
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);
    }
}

/**
 * 创建一个默认容量为 10 的 ArrayList
 * DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为之前说到的默认容量下的空数组
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * 使用一个 Collection 来创建 ArrayList ,如果 Collection 长度为 0 ,则将 EMPTY_ELEMENTDATA 赋值给 elementData
 * 以 Collection 的迭代器返回值的顺序建立
 */
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 {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

关键方法

public boolean add(E e)

​ 向集合容器内添加一个元素,位置为末尾。

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

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

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

    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    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(E e) 方法向容器中添加一个元素,首选会检查容器的容量是否足够,在添加时需要的容量是现在的容量 size + 1ensureCapacityInternal 方法完成上述操作,先调用 calculateCapacity 方法,这个方法会判断当前的 elementData 是不是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,如果是则返回 DEFAULT_CAPACITY,否则返回 size + 1;之后 ensureExplicitCapacity 方法判断是否需要进行扩容,如果需要则扩容的原来的三倍,如果还是不够就扩容到 minCapacity 大小。扩容过程会尽可能往大扩容,最大为 Integer.MAX_VALUE

​ 之后就是在 elementData 数组的最后放置需要插入的元素,将 size 增加。

public void add(int index, E element)

​ 与 add(E e) 类似,可以指定元素插入的位置,但是 index 必须 小于 size 大于 0

public void add(int index, E element) {
	// 检查 index 的范围 0 <= index <= size
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

​ 此外还有使用 Collection 批量插入的方法,与上面两个类似。

public void clear()

​ 这个方法可以清空容器里的数据,可以看到实现非常简单。

public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

public boolean contains(Object o)

​ 此方法判断指定的对象是否在容器中,间接调用了 indexOf 方法来实现。indexOf 对于容器中存在的数据返回最小下标,不存在则返回 -1lastIndexOf 则返回最大下标。

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

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

public E get(int index)

​ 此方法可以获取指定索引位置对应的元素值。需要注意的时 rangeCheck 只要求 index < size ,将负值情况交给 ArrayIndexOutOfBoundsException 处理。

public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

E elementData(int index) {
    return (E) elementData[index];
}

public E remove(int index)

​ 删除指定位置上的元素,并返回元素的值,同样与指定插入位置一样, index 必须 小于 size 大于 0

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)

​ 删除指定对象,使用 equals 进行判断是否相等。删除过程中使用 fastRemove 进行删除。fastRemove 方法省去了边界检查和返回旧值。

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
}

public E set(int index, E element)

​ 此方法可以将已经存在的数据修改为指定数据, index 必须 小于 size 大于 0

public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

总结

​ ArrayList 在 Java 集合中算是比较简单的了,它与 Vector 类似,但 Vector 是线程安全的,而 ArrayList 不是。此外,ArrayList 的迭代器都是支持 fast-fail 机制,在并发访问修改下,可能会抛出异常。总体来说, ArrayList 的源码是通俗易懂的,相对简单直白。

发布了14 篇原创文章 · 获赞 0 · 访问量 109

猜你喜欢

转载自blog.csdn.net/aaacccadobe/article/details/103929945