java容器源码分析(三)——ArrayList

原创
2015/12/16 17:43
阅读数 158

本文内容:

  1. ArrayList概述

  2. ArrayList源码分析

ArrayList概述


ArrayList底层是用数组实现的,元素到达数组大小,则动态扩容。ArrayList的继承关系图如下:

继承了AbstractList类,实现了RandomAccess,Cloneable,List,Serializable接口。

Cloneable,List,Serializable接口我们在上一篇文章中说过,这里不再说明,RandomAccess见名知意,它表示该类支持随机访问(random access),具体定义如下:

public interface RandomAccess {

}

Marker interface used by List implementations to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.

注释说这个接口是用来做标志的,恩,就是让人看的。


ArrayList源码分析


构造函数

public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
}

调用的父类的构造函数,看看父类的构造函数怎么样的:

/**
 * Sole constructor.  (For invocation by subclass constructors, typically
 * implicit.)
 */
protected AbstractList() {
}

AbstractList的构造函数,什么都没有,不用管。继续看ArrayList的构造函数this.elementData=EMPTY_ELEMENTDATA,初始化了elementData,看这两个变量的定义:

/**
 * Shared empty array instance used for empty instances.
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to
 * DEFAULT_CAPACITY when the first element is added.
 */
private transient Object[] elementData;

其中EMPTY_ELEMENTDATA是所有空ArrayList都共享的(构造函数的这句话this.elementData=EMPTY_ELEMENTDATA)。然后elementData是放list的实际内容。ok,这就是ArrayList的底层是数组实现的真正含义。那数组有固定大小,我们在平常使用中好像没有说一开始给定大小呢!这是如何实现的呢?请继续看。

add方法

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

看来是ensureCapacityInternal方法对数据的大小进行了动态变化。

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

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

    // overflow-conscious code
    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);
}

看上面的代码,如果elementData的长度小于minCapacity(数组内容),则调用grow 方法增加容量。增长规则看grow函数就一目了然了。

 最后调用了Arrays.copyOf方法,它的用法如何呢?

get方法

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

    return elementData(index);
}
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// Positional Access Operations

@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

get方法就像数组取下标一样简单!

add(int index, E element)

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

通过System.arraycopy方法,将elementData从index开始的size-index个元素复制到index+1至size+1的位置(即index开始的元素都向后移动一个位置),然后将index位置的值指向element。       

remove(int index)

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

额,相当于index后的元素左移。

removeAll(Collection<?> c)

public boolean removeAll(Collection<?> c) {
    return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}

batchRemove这样用finally,是不是有点亮瞎眼呢?

subList(int fromIndex, int toIndex)

返回[fromIndex,toIndex)的子列表。

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

看上面源码,是返回了一个与ArrayList不相同的类,但 共享相同的数据,也就是说改变sublist的值,也会让原列表的改变。

总结

ArrayList应该是平常用的最多的一个List容器实现了,平常用的也基本就是add,get两个方法。看jdk源码确实发现其有较多值得学习之处,但有时又觉得jdk实现得有点臃肿!




展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部