ArrayList源碼分析

java中的數據結構源碼分析的系列文章:
ArrayList源碼分析
LinkedList源碼分析java

1、簡述

咱們知道,數據結構中有兩種存儲結構,分別是:順序存儲結構(線性表)、鏈式存儲結構(鏈表),在java中,對這兩種結構分別進行實現的類有:數組

  • 順序存儲結構:ArrayList、Stack
  • 鏈式存儲結構:LinkedList、Queue

本篇只對ArrayList的源碼進行分析,對於其餘類的源碼分析可經過本人博客列表進行查看。bash

2、分析

List

在分析ArrayList以前,咱們先來看看集合的接口——List。數據結構

public interface List<E> extends Collection<E> {
    boolean add(E e);
    void add(int index, E element);
    boolean remove(Object o);
    E remove(int index);
    E set(int index, E element);
    E get(int index);
    ...
}複製代碼

在List這個接口中,提供了對集合進行操做的增、刪、改、查方法,咱們知道,ArrayList和LinkedList都實現了List接口,但它們的底層實現分別是線性表與鏈表,因此,對應的增、刪、改、查方法確定也不同,下面的分析也將從這幾個方法入手。dom

ArrayList

一、成員變量

在ArrayList的源碼中,成員變量並很少,下面就截出其中幾個重要的變量進行說明。函數

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    ...
    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    private int size;
    ...
}複製代碼
  • DEFAULT_CAPACITY:默認的數組長度
  • EMPTY_ELEMENTDATA:默認的空數組
  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA:默認的空數組(與EMPTY_ELEMENTDATA有點區別,在不一樣的構造函數中用到)
  • elementData:真正用於存放數據的數組
  • size:數組元素個數

ArrayList的底層實現是數組,數組一定是有限長度的,ArrayList中默認的數組大小是10。源碼分析

二、構造函數

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}複製代碼

這個構造函數是開發最經常使用的,能夠看到,它僅僅只是讓elementData等於一個空數組(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)而已。post

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);
    }
}複製代碼

這個構造函數能夠指定初始化數組的長度,當initialCapacity大於0時,爲elementData建立一個長度爲initialCapacity的Object數組;當initialCapacity等於0時,則讓elementData等於一個空數組(EMPTY_ELEMENTDATA)。ui

三、增

1)add(E e)

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

在前面已經提到了,size是一個成員變量,表示ArrayList中的元素個數。在這個方法中,先調用了ensureCapacityInternal()方法確保數組有足夠的容量,再對將元素添加到elementData數組中。下面就來看看ensureCapacityInternal()方法是如何確保數組有足夠的容量的。this

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

    ensureExplicitCapacity(minCapacity);
}複製代碼

該方法結合ensureExplicitCapacity()方法,總的來講就是計算並擴大最小的容器體積。

這裏就用到了DEFAULTCAPACITY_EMPTY_ELEMENTDATA這個空數組,若是此時elementData與DEFAULTCAPACITY_EMPTY_ELEMENTDATA相等,說明開發者使用的是無參構造函數建立了集合,並且是添加第一個元素,此時的容器大小爲0。

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

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

當minCapacity - elementData.length > 0時,說明當前數組(容器)的空間不夠了,須要擴容,因此調用grow()方法。

modCount只是一個計數變量而已,源碼中有不少地方出現,無須理會。

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);
}複製代碼

grow()方法是用來給ArrayList集合進行擴容的,它計算出新的容器大小(即newCapacity),並確保了newCapacity不會比minCapacity小,最後調用Arrays.copyOf()建立一個新的數組並將數據拷貝到新數組中,最後讓elementData進行引用。

oldCapacity >> 1 等價於 oldCapacity / 2,也就是說ArrayList默認的擴容大小是當前數組大小的一半。

2)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++;
}複製代碼

通過對add(E e)方法進行分析,這個增長方法就很容易理解了,它先確保容器有足夠大的空間,沒有就擴容,而後將elementData數組中從index位置開始的全部元素日後"移動"1位,再對數組的index位置進行元素賦值,最後將記錄集合中元素個數的size變量加1。

四、刪

1)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;
}複製代碼

numMoved表示在執行刪除操做時數組須要移動的元素個數,將elementData數組中從index後一位開始的全部元素(即numMoved個元素)往前"移動"1位(這樣一移動,index位置的元素會被後面的元素覆蓋,間接起到了刪除元素的做用),而後把最後的那個元素置空,同時將size變量減1。

2)remove(Object o)

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(int index)基本一致,這裏就再也不說明了。(fastRemove()方法的代碼不是能夠複用麼。。。)

五、查 & 改

ArrayList的修改和獲取元素的方法至關簡單,就是對elementData數組進行簡單操做罷了,這裏就列出源碼看看就行了。

1)get(int index)

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

E elementData(int index) {
    return (E) elementData[index];
}複製代碼

2)set(int index, E element)

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

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}複製代碼

3、總結

  1. ArrayList底層實現是數組。
  2. 當使用無參數構造函數建立ArrayList對象時,ArrayList對象中的數組初始長度爲0(是一個空數組)。
  3. ArrayList的擴容策略是每次都增長當前數組長度的一半(非固定分配)。
  4. ArrayList的擴容方式是直接建立一個新的數組,並將數據拷貝到新數組中。
相關文章
相關標籤/搜索