JDK源碼分析系列02---ArrayList和LinkList

ArrayList和LinkList的源碼分析

概要

ArrayList和LinkList是經常使用的存儲結構,不看源碼先分析字面意思,Array意思是數組,可知其底層是用數組實現的,Link意思是連接,可知是以鏈表實現,這兩種數據結構各有什麼特色呢?在實際開發中,咱們要如何選擇?java

1.ArrayList

  • ArrayList是實現了List接口的可變數組,即動態數組,它不只實現了List的可選操做,同時容許元素爲null,它提供了一些方法來操做數組的大小來保存元素,該類大體至關於Vector,只是ArrayList不是線程安全的.
  • 每一個ArrayList實例都有一個容量capacity,它是存儲元素的數組的大小,其值至少等於list的size,當元素添加到ArrayList中,它的capacity會自動增大.
  • 注意ArrayList的實現是非線程安全的,若是多個線程同時訪問ArrayList實例,且至少有一個線程修改了元素,那麼必需要加鎖synchronized.
  • 下面是ArrayList的類關係圖,能夠看出其繼承了AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable接口.

1.1基本屬性

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 默認初始容量10
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空實例的空數組
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 默認空實例的空數組
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存儲元素的數組,它的大小就是ArrayList的容量,不用序列化
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList元素的大小
     */
    private int size;
}

1.2構造器

/**
 * 構造空的list,並指定初始容量
 *
 * @param  initialCapacity  初始容量
 * @throws IllegalArgumentException 若是初始容量爲負數
 */
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);
    }
}

/**
 * 構造空的list,並指定初始容量10
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * 構造list,包含指定集合的全部元素
 *
 * @param c 指定的集合
 * @throws NullPointerException 若是指定集合爲null
 */
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;
    }
}

ArrayList list = new ArrayList(),默認容量爲10,若是添加超過10個元素,就會形成list的擴容,內部會建立一個新的數組,對效率會有影響,若是有大量的元素添加,那麼list就會頻繁擴容,效率低下.所以在開發中能夠指定初始容量,細節之中能夠看出一我的的基本功.數組

1.3經常使用方法

/**
 * 返回list元素的數量
 */
public int size() {
    return size;
}

/**
 * 判斷list是否包含元素,返回true或false
 */
public boolean isEmpty() {
    return size == 0;
}

/**
 * 判斷是夠包含指定的元素,返回true或false
 */
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

/**
 * 返回元素第一次出現的索引index
 * 若是list沒有這個元素,返回-1,不然返回第一次出現的index
 * 注意:也能夠查詢null的索引
 */
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;
}

/**
 * 返回元素最後一次出現的索引index
 * 若是list沒有這個元素,返回-1,不然返回最後一次出現的index
 * 注意:也能夠查詢null的索引
 */
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;
}

/**
 * 返回指定索引的元素
 * @param  index 索引
 * @return 元素
 * @throws IndexOutOfBoundsException, 若是index超出數組的範圍
 */
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

/**
 * 替換指定索引的元素
 *
 * @param index 索引
 * @param element 新元素
 * @return 老元素
 * @throws IndexOutOfBoundsException 
 */
public E set(int index, E element) {
    rangeCheck(index);

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

下面重點介紹add()和remove()方法,元素的新增和刪除內部是如何實現的呢?安全

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

ArrayList新增元素時,也就是在list的尾部添加一個元素,首先修改元素的數量+1數據結構

ensureCapacityInternal(size+1),即list的size+1,元素數量加1,詳細實現以下:dom

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

元素的數量+1後,判斷minCapacity有沒有超出當前的容量,若是超出了,就要進行擴容操做grow()源碼分析

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

其邏輯以下:新容量 = 老容量 + 老容量右移1位(即除以2),也就是大約1.5倍的老容量,爲何說大約呢?由於若是老容量是偶數,那麼新容量正好等於1.5倍老容量,若是老容量是奇數11,那麼新容量是15.
容量在大,也是有限制的,最大MAX_ARRAYSIZE = Integer.MAXVALUE - 8,有21億,估計沒有人會放這麼多的數據吧. 性能

elementData[size++] = e, 而後把新元素e放在新索引的位置,也就是數組的尾部. this

ArrayList刪除元素有兩種,一種是根據索引刪除,二是直接刪除對象 線程

  • 根據索引刪除對象 3d

    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 = 4 - 2- 1 = 1;
    System.arraycopy(elementData, 3, elementData, 2,1);
    流程圖以下:

本質上是把要刪除的元素替換爲它後面的元素,而後把最後一個元素賦值爲null,手動GC,返回老的元素.

  • 直接刪除對象

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

    就是循環遍歷數組,當元素第一次出現的時候,刪除這個元素,同時返回true,若是元素不存在,那麼數組不改變,同時返回false.

2.LinkList

  • LinkList是List接口的雙向鏈表實現,不只實現了List的方法,同時容許元素爲null
  • 獲取index索引的元素時,會從鏈表的頭部或尾部進行查找,哪邊近從哪邊開始
  • 注意該實現是線程非安全
    下面是LinkList的類關係圖,它繼承了AbstractSequentialList,實現了List, Deque, Cloneable, java.io.Serializable接口

2.1基本屬性

public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
    transient int size = 0;

    /**
     * 第一個節點,不用序列化
     */
    transient Node<E> first;

    /**
     * 最後一個節點,不用序列化
     */
    transient Node<E> last;
}

2.2構造器

/**
 * 構造空的list
 */
public LinkedList() {
}

/**
 * 構造指定集合的list
 * @param  c 集合
 * @throws NullPointerException 若是集合爲null
 */
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

2.3經常使用方法

添加元素add()

/**
 * 在LinkList的尾部添加元素
 * 這個方法至關於addLast
 * 返回true/false
 */
public boolean add(E e) {
    linkLast(e);
    return true;
}

linkLast的具體實現以下:

void linkLast(E e) {
    //l賦值爲last節點
    final Node<E> l = last;
    //建立新的節點e,前面元素是l,後面是null
    final Node<E> newNode = new Node<>(l, e, null);
    //把新的節點標記爲last節點
    last = newNode;
    //判斷是否是第一個節點
    //若是是,新的節點就是第一個節點
    if (l == null)
        first = newNode;
    else
    //若是不是,以前的最後一個節點後面是新的節點
        l.next = newNode;
    size++;
    modCount++;
  }

刪除元素remove()

public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

從頭部遍歷全部的節點,若是當前節點元素與要刪除的節點元素相同,那麼移除當前節點,若是LinkList包含多個要刪除的元素,那麼只會刪除index較小的那個節點.

3.總結

  1. ArrayList實現了RandomAccess,隨機讀取數據速度較快,它有索引index,會直接讀取數組的索引,效率很高,可是新增和刪除元素,內部進行數組的動態複製,效率低,若是遇到大量的數據add操做,頻繁擴容,性能更差
  2. LinkList新增元素就是建立了一個新的節點,只要把last節點指向最後一個元素便可,效率很高,刪除元素,就是移除指定的節點,同時調整先後的節點便可,可是對於隨機訪問元素,它須要判斷當前index離頭部和尾部哪一個更近,而後去依次查找,效率低下
  3. 因此對於隨機快速讀取數據,可使用ArrayList,對於快速新增和刪除元素,可使用LinkList
相關文章
相關標籤/搜索