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

本文內容:java

  1. ArrayList概述數組

  2. ArrayList源碼分析app

ArrayList概述dom


ArrayList底層是用數組實現的,元素到達數組大小,則動態擴容。ArrayList的繼承關係圖以下:
ide

繼承了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.
學習

註釋說這個接口是用來作標誌的,恩,就是讓人看的。
this


ArrayList源碼分析spa


構造函數

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實現得有點臃腫!

相關文章
相關標籤/搜索