關於LinkedList經驗分享(源碼分析)

集合的作用就是以一定的方式組織,存儲數據。對於LinkedList,我認爲需要的關注以下內容:
 1.集合的基本存儲單元
 2.集合的增刪改查基本操作的實現
 3.存儲數據的要求,是否爲空,是否允許重複
 4.存放與讀取是否有序
 5.是否線程安全

基本存儲單元

 LinkedList的源碼片段:

    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

雙向鏈表實現,鏈表長度size,頭指針first,尾指針last。

集合的增刪改查基本操作的實現

1.新增元素

    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

新建一個l節點指向當前last節點,新建一個newNode節點,將newNode的prev指向l地址,last節點指向newNode,如果鏈表中無數據,將first節點指向newNode,如果存在數據,將l節點指向newNode節點,這樣就實現了l與newNode的雙向鏈接。

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

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    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++;
    }

插入元素時,當插入的是最後一個元素時(即順序插入),和add方法一致,這裏分析下中間插入。新建一個pred節點指向當前succ節點的上一個位置,新建newNode節點,newNode的prev指向pred,next指向succ,此時newNode節點已經鏈接了前後節點,接下來需要將前節點的next指向newNode,及將後節點的prev指向newNode,這樣,newNode的雙向鏈接就完成了。

2.修改元素

    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }


用node方法取出節點,修改val,後面分析node方法。


3.刪除元素

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

先找出當前節點x的前後節點,prev節點及next節點。將prev的next指向next節點,將next的prev指向prev節點,最後將x的prev和next設置爲null。

4.尋址

    Node<E> node(int index) {
        // assert isElementIndex(index);

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

尋址方式比較重要,這裏用了位運算,加快速度。當index小於長度的一般,從頭開始尋址;反之從尾部開始尋址。


其實鏈表操作的分析最好在紙上畫出來,先找出當前節點的前後節點,更改鏈接的方向,一目瞭然。

存儲數據的要求,是否爲空,是否允許重複

允許爲空或者null,允許重複

存放與讀取是否有序

底層實現是雙向鏈表,所以有序

是否線程安全

非線程安全,因爲所有方法都是不同步的。

LinkedList與ArrayList的比較

LinkdList基於Node數組的實現,每次插入都需new一個Node對象,並維護一些引用地址,所以耗費的空間和時間比ArrayList要大。但是當集合的數據量變大,而且需要中間插入數據的操作變多,ArrayList就需要耗費大量時間和空間去進行數據copy,大大降低了性能,這時LinkedList的優勢就顯現出來了。總體來說,LinkedList快在維持引用關係,慢在尋址,但ArrayList快在尋址,慢在數組的copy。

再說一下迭代的問題,ArrayList實現了RandomAccess接口,支持快速隨機訪問,ArrayList在隨機或連續訪問列表是都有良好的性能,for和foreach的速度都很快。LinkedList在用foreach,即Iteratior迭代器訪問時有良好的性能表現,但在普通for循環時,速度會慢的讓人發狂。

以下爲測試結果,單位ms:

init arrayList : 62
init linkedList : 47
init finished!
test005 ArrayList 普通for循環速度(10W條數據): 1
test006 ArrayList Iterator循環速度(10W條數據): 16
test007 LinkedList 普通for循環速度(10W條數據): 37781
test008 : LinkedList Iterator循環速度(10W條數據)16







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章