1. 程式人生 > >java集合的實現細節--ArrayList和LinkedList

java集合的實現細節--ArrayList和LinkedList

方法 封裝 對象 通過 場景 部分 index索引 ava 本質

  ArrayList和LinkedList的實現差異

  List代表一種線性表的數據結構,ArrayList則是一種順序存儲的線性表,ArrayList底層采用動態數組的形式保存每一個集合元素,LinkedList則是一種鏈式存儲的線性表,其本質上就是一個雙向鏈表,它不僅實現了List接口,還實現了Deque接口,Deque代表了一種雙端隊列,既具有隊列(FIFO)的特性,也具有棧(FILO)的特性,也就是說,LinkedList既可以當成雙向鏈表使用,也可以當成隊列使用,還可以當成棧來使用。

public class LinkedList<E>
extends AbstractSequentialList<E>
implements
List<E>, Deque<E>, Cloneable, java.io.Serializable

  ArrayList底層采用一個elementData數組來保存所有集合元素,因此ArrayList在插入元素時需要完成兩件事情:

  ①、保存ArrayList底層封裝的數組長度大於集合元素的個數

  ②、將插入位置之後的所有數組元素“整體搬家”,向後移動一“格”

  當刪除ArrayList集合中指定位置的元素時,程序也要進行“整體搬家”,而且還需要將被刪除索引處的數組元素賦值為null,下面是ArrayList集合的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; }

  對於ArrayList集合而言,當程序向ArrayList中添加、刪除集合元素時,ArrayList底層都是需要對數組進行“整體搬家”,因此性能很差。

  但如果程序調用get(int index)方法取出ArrayList集合中的元素時,性能和數組幾乎相同,效率非常快。下面是ArrayList集合的get(int index)方法的源代碼。

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

  LinkedList本質上是一個雙向鏈表,因此它使用內部類來保存每一個集合元素。

 private static class Node<E> {
     //集合元素 E item;
     //保存指向下一個鏈表節點的引用 Node
<E> next;
     //保存指向上一個鏈表節點的引用 Node
<E> prev;      //普通構造器 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }

  從上面程序中的粗體字代碼可以看出,一個Node對象代表雙向鏈表的一個節點,該對象中的next變量指向下一個節點,previous子指向上一個節點。

  由於LinkedList采用雙向鏈表來保存集合元素,因此它在添加集合元素時,只要對鏈表進行插入即可。下面是LinkedList添加節點的源碼。

     public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

  從上面代碼看出,由於Linked本質上就是一個雙向鏈表,因此它非常方便的在指定節點之前插入新節點。

  上面add(int index,E element)方法實現中用到了以下三個方法。

  ①、node(int index):搜索指定索引出的元素

  ②、linkLast(E e):將e新節點插入到最後

  ③、linkBefore(E e,Node<E> succ):在succ節點之前插入e新節點

  node(int index)實際上就是get(int index)方法的底層實現。Linked必須逐個元素的搜索,直到找到第index個元素為止。node(int index)采用二分法的方式進行查找,以下是該方法的源代碼。

   Node<E> node(int index) {
        // assert isElementIndex(index);
     //如果index小於size/2
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

  上面的node(int index)方法就是逐個元素的找到index索引處的元素,只是由於LinkedList是一個雙向鏈表,因此程序先根據index的值判斷它離鏈表頭近還是離鏈表尾近,如果離鏈表頭近則從頭端開始搜索,如果離鏈表尾近則從尾端開始搜素。

  Linked的get(int index)方法只是對上面node(int index)方法的簡單包裝。get(int index)方法的源代碼如下。

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

  但單純的插入操作就比較簡單了,只要修改幾個節點裏面的previous、next引用的值即可。下面是linkedbefore(E e, Node<E> succ)方法的源代碼。

   void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

  如果只是單純的添加某個節點,那麽LinkedList的性能是非常好的,但如果需要向某個指定索引處添加節點,LinkedList必須先找到指定索引處的節點,這個搜索過程的系統開銷並不小,因此LinkedList的add(int index,E e)方法的性能並不是特別好。如果只使用LinkedList的addFrist(E e)、addLast(E e)、offerFrist(E e)、offerLast(E e)、pollFrist(E e)、pollLast(E e)的話,性能是非常好的,因為可以避免搜索過程。

  類似的,LinkedList為了實現remove(int index)方法,也必須先通過node(int index)方法找到index索引處的節點,然後修改它前一個節點的next引用,以及後一個節點的previous引用,下面是該方法的源代碼。

    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

  綜上所述,ArrayList、LinkedList有各自適用的場景,大部分情況下,ArrayList的性能總是優於Linkedlist,因此絕大部分都應該考慮使用ArrayList集合。但如果程序荊楚需要添加、刪除元素,尤其是經常需要add(E e)方法向集合中添加元素,則應該考慮使用LinkedList集合了。

java集合的實現細節--ArrayList和LinkedList