jdk源碼之java集合類(二)——List家族

在一年多前我曾經簡單地記錄了一部分集合類源碼,主要集中於ArrayList、Vector、HashMap、HashTable這幾個常用常被提問的點。從這篇開始我將對集合家族的每個小家庭進行一個簡單地描繪,首先我們從我們的線性表——list家族開始聊起,文章將會從淺到深一層層地直到源碼解析。

一.族譜

上圖:

這裏我把List家族的最主要成員全部都畫進去了,來看下。

        a) 家族的最大的祖宗——Iterable,我們知道Iterate是一個迭代器,迭代器通常是用來遍歷的,那麼Iterable顧名思義就是可以用來迭代的東西。順帶一提,這裏可以看出接口的命名方式的一些特點,有一部分的接口(尤其是核心於某一功能的接口)會以其擁有功能的名稱化命名,如run方法對應Runnable、call方法對應Callable、listen方法對應Listener。而這裏的Iterable自然包含了方法iterator咯:

    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();

方法用於生產迭代器,就不解釋了。

       b) 家族長老Collection,這可以說是幾乎所有一元集合的爸爸,其主要作用,申明瞭一些通有操作:size啊,isEmpty啊,toArray啊;

       c) 線性集合的大家長List,主要作用申明瞭一些線性集合一定會幹的事情:add啊、addAll啊、clear啊、remove啊。。。。;

       d) 線性集合的一個通用的簡單地實現類AbstractList,他實現了addAll、size、isEmpty、clear等操作,並將參數較少的add等操作也實現,將參數最全的add、remove、get等操作留給子類實現。

       這裏插播一下一個標準類族的設計原則,集合類的類族是非常具有代表性的,首先在接口層面就已經定義好了不同性質的集合,如將Iterable作爲最高層接口,由Collection去繼承,然後由List、Queue、Set去繼承Collection,將不同類型集合的特點異同展現到了類族的設計中,不僅可以方便程序員閱讀理解,並且可以方便其實現類進行統一化的操作;而對於同一種類型的集合List來說,將其可以統一的操作在抽象類中完成,由於其實現類只是在數據結構上有區別,因此只需將最基本(參數最全面)的增刪改查操作在具體實現類中完成,而剩下的操作則可以通過調用這些被子類實現的抽象方法,而進行統一的實現。

       e) ArrayList,最基本的線性實現。

       f) Vector,加鎖的線性實現。

       g) Stack, 利用Vector特性去完成棧結構。

       h) LinkedList,鏈表實現。

       i)CopyOnWriteArrayList,利用一種近乎CAS的原理實現線程安全的線性實現。

這裏可能需要解釋一下,ArrayList、Vector、CopyOnWriteArrayList三者的區別:

      1)ArrayList是最基本的實現,因此很明顯他是線性不安全的。

      2)Vector是加鎖實現,在寫入操作時候比ArrayList多了個線程鎖,因此他是線性安全的,但是由於加了鎖,所以性能開銷大,整體操作性能比ArrayList低的多。

      3)CopyOnWriteArrayList,爲了解決上面兩個東西的矛盾,在jdk1.5中增加,利用CAS的原則,在寫入的時候先拷貝原集合,在寫入成功後查看原集合是否改變,若未改變則將原集合的指針(類似指針的東西)指向新的集合,但是如果變了,就將操作回滾,重新加入。這樣的好處是,如果有寫操作同時進行,後期的回滾相當於鎖的釋放;而若寫操作沒有同時進行,則不需要加鎖開銷就可以直接寫入,即達到線程安全,又不會造成太大開銷。

接下來,我們就來通過源碼解讀這個家族的具體實現吧。

二.AbstractList

如上文所說,abstractList完成了一些相互調用的操作,如add:

    public boolean add(E e) {
        add(size(), e);
        return true;
    }

通過調用參數更多的add來完成,而參數最全的add則交由子類實現。

如clear:

    public void clear() {
        removeRange(0, size());
    }

    protected void removeRange(int fromIndex, int toIndex) {
        ListIterator<E> it = listIterator(fromIndex);
        for (int i=0, n=toIndex-fromIndex; i<n; i++) {
            it.next();
            it.remove();
        }
    }

通過調用迭代器的remove來完成。

並且他還定義了List的通用迭代器:

    private class Itr implements Iterator<E> {
        /**
         * Index of element to be returned by subsequent call to next.
         */
        int cursor = 0;

        /**
         * Index of element returned by most recent call to next or
         * previous.  Reset to -1 if this element is deleted by a call
         * to remove.
         */
        int lastRet = -1;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size();
        }

        public E next() {
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public E previous() {
            checkForComodification();
            try {
                int i = cursor - 1;
                E previous = get(i);
                lastRet = cursor = i;
                return previous;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor-1;
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.set(lastRet, e);
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                AbstractList.this.add(i, e);
                lastRet = -1;
                cursor = i + 1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

你就會發現,他巧妙地調用了外部類(AbstractList)定義的抽象方法(或直接拋異常留個子類覆蓋的方法):add、get、set;去完成迭代遍歷的作用。

於是乎,一個擁有一系列可以方便開發人員操作的List接口的一大堆操作,則被簡化成了只有最完整增刪改查操作的抽象方法。接下來只需要在子類中繼續實現基本方法即可。

三.ArrayList和Vector

這兩個類的源碼,我在上一篇講到集合的內容的時候就已經解析過了,jdk源碼之java集合類(一)

這裏簡單總結一下就是:

這兩玩意裏面都放了一個數組,當然數組的類型定義是泛型啦;然後在初始化的時候,要麼傳入個長度,要麼不傳入用默認的長度,然後在add的時候,如果超過了長度,調用Arrays.copy這個無敵萬能的方法,去實現數組的變長(由於數組長度原本是final的,所以不能自己變),而變得規則是ArrayList爲1.5倍,Vector爲2倍。並且在寫操作的時候,Vector有synchronize關鍵字,所以是線性安全的。

完了!

四.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;

定義幾個參數,其中有點意思的就是Node,Node是鏈表節點;

    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的內部設計,有前有後,雙向鏈表,標準的數據結構實現。

而這個類中最爲重要的一堆方法就是:

/**
     * Links e as first element.
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

把鏈表頭改了。

    /**
     * Links e as last element.
     */
    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++;
    }

把鏈表尾巴改了。

    /**
     * Inserts element e before non-null Node 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++;
    }

在節點前加個節點。

    /**
     * Unlinks non-null first node f.
     */
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

把鏈表頭刪了。

   /**
     * Unlinks non-null last node l.
     */
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

把表尾刪了。

    /**
     * Unlinks non-null node x.
     */
    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;
    }

去掉個節點。

好了,接下來麼,改幹啥幹啥,什麼add啊remove啊get啊set啊,只需要調用這堆東西就完了。

五.先進行個小結

直到現在爲止,我們已經把這個List的類族結構搞得比較清楚了,現在唯一還沒有詳細解釋的常用集合就是一個基於類似CAS設計的CopyOnWriteArrayList了,但是基本原理我們已經在上文中講述過了,至於其代碼的詳解,由於其並不在java.util包直接管理中(在java.util.concurrent中),所以我們放到CAS的文章再做詳解。

總體而言,集合類的代碼還是比較通俗易懂的,但是其類族設計也是十分經典的,閱讀集合類代碼,既能學習設計理念,又可以鞏固基礎數據結構知識,而且在將來使用中也可以正確選擇合理的集合使用。

ps:以後還會更新Set、Queue和Map的類族解析。

 

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