LinkedList源碼解析

此文已由做者趙計剛受權網易雲社區發佈。算法

歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗。數組


1、對於LinkedList須要掌握的八點內容安全

  • LinkedList的建立:即構造器服務器

  • 往LinkedList中添加對象:即add(E)方法源碼分析

  • 獲取LinkedList中的單個對象:即get(int index)方法this

  • 修改LinkedList中的指定索引的節點的數據set(int index, E element)spa

  • 刪除LinkedList中的對象:即remove(E),remove(int index)方法.net

  • 遍歷LinkedList中的對象:即iterator,在實際中更經常使用的是加強型的for循環去作遍歷線程

  • 判斷對象是否存在於LinkedList中:contain(E)指針

  • LinkedList中對象的排序:主要取決於所採起的排序算法(之後講)

2、源碼分析

2.一、LinkedList的建立

實現方式:

List<String> strList0 = new LinkedList<String>();

源代碼:在讀源代碼以前,首先要知道什麼是環形雙向鏈表,參考《算法導論(第二版)》P207

    private transient Entry<E> header = new Entry<E>(null, null, null);//底層是雙向鏈表,這時先初始化一個空的header節點
    private transient int size = 0;//鏈表中的所存儲的元素個數

    /**
     * 構造環形雙向鏈表
     */
    public LinkedList() {
        header.next = header.previous = header;//造成環形雙向鏈表
    }

Entry是LinkedList的一個內部類:

    /**
     * 鏈表節點
     */
    private static class Entry<E> {
        E element;            //鏈表節點所存儲的數據
        Entry<E> next;        //當前鏈表節點的下一節點
        Entry<E> previous;    //當前鏈表節點的前一個節點

        Entry(E element, Entry<E> next, Entry<E> previous) {
            this.element = element;
            this.next = next;
            this.previous = previous;
        }
    }

執行完上述的無參構造器後:造成的空環形雙向鏈表以下:


其中,左上角爲previous,右下角爲next

2.二、往LinkedList中添加對象(add(E e))

實現方式:

strList0.add("hello");

源代碼:

    /**
     * 在鏈表尾部增長新節點,新節點封裝的數據爲e
     */
    public boolean add(E e) {
        addBefore(e, header);//在鏈表尾部增長新節點,新節點封裝的數據爲e
        return true;
    }

    /*
     * 在鏈表指定節點entry後增長新節點,新節點封裝的數據爲e
     */
    private Entry<E> addBefore(E e, Entry<E> entry) {
        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
        newEntry.previous.next = newEntry;//新節點的前一個節點的下一節點爲該新節點
        newEntry.next.previous = newEntry;//新節點的下一個節點的前一節點爲該新節點
        size++;            //鏈表中元素個數+1
        modCount++;        //與ArrayList相同,用於在遍歷時查看是否發生了add和remove操做
        return newEntry;
    }

在添加一個元素後的新環形雙向鏈表以下:


在上述的基礎上,再調用一次add(E)後,新的環形雙向鏈表以下:


這裏,結合着代碼註釋與圖片去看add(E)的源代碼就好。

注意:在添加元素方面LinkedList不須要考慮數組擴容和數組複製,只須要新建一個對象,可是須要修改先後兩個對象的屬性。

2.三、獲取LinkedList中的單個對象(get(int index))

 實現方式:

strList.get(0);//注意:下標從0開始

源代碼:

    /**
     * 返回索引值爲index節點的數據,index從0開始計算
     */
    public E get(int index) {
        return entry(index).element;
    }

    /**
     * 獲取指定index索引位置的節點(須要遍歷鏈表)
     */
    private Entry<E> entry(int index) {
        //index:0~size-1
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index:"+index+", Size:"+size);
        Entry<E> e = header;//頭節點:既做爲頭節點也做爲尾節點
        if (index < (size >> 1)) {//index<size/2,則說明index在前半個鏈表中,從前日後找
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {//index>=size/2,則說明index在後半個鏈表中,從後往前找
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
    }

注意:

  • 鏈表節點的按索引查找,須要遍歷鏈表;而數組不須要。

  • header節點既是頭節點也是尾節點

  • 雙向鏈表的查找,先去判斷索引值index是否小於size/2,若小於,從header節點開始,從前日後找;若大於等於,從header節點開始,從後往前找

  • size>>1,右移一位等於除以2;左移一位等於乘以2

2.四、修改LinkedList中指定索引的節點的數據:set(int index, E element)

使用方式:

strList.set(0, "world");

源代碼:

    /**
     * 修改指定索引位置index上的節點的數據爲element
     */
    public E set(int index, E element) {
        Entry<E> e = entry(index);//查找index位置的節點
        E oldVal = e.element;//獲取該節點的舊值
        e.element = element;//將新值賦給該節點的element屬性
        return oldVal;//返回舊值
    }

注意:entry(int index)查看上邊

2.五、刪除LinkedList中的對象

2.5.一、remove(Object o)

使用方式:

strList.remove("world")

源代碼:

    /**
     * 刪除第一個出現的指定元數據爲o的節點
     */
    public boolean remove(Object o) {
        if (o == null) {//從前日後刪除第一個null
            //遍歷鏈表
            for (Entry<E> e = header.next; e != header; e = e.next) {
                if (e.element == null) {
                    remove(e);
                    return true;
                }
            }
        } else {
            for (Entry<E> e = header.next; e != header; e = e.next) {
                if (o.equals(e.element)) {
                    remove(e);
                    return true;
                }
            }
        }
        return false;
    }

    /*
     * 刪除節點e
     */
    private E remove(Entry<E> e) {
        //header節點不可刪除
        if (e == header)
            throw new NoSuchElementException();

        E result = e.element;
        //調整要刪除節點的先後節點的指針指向
        e.previous.next = e.next;
        e.next.previous = e.previous;
        //將要刪除元素的三個屬性置空
        e.next = e.previous = null;
        e.element = null;
        
        size--;//size-1
        modCount++;
        return result;
    }

注意:

  • header節點不可刪除

 2.5.二、remove(int index)

使用方式:

strList.remove(0);

源代碼:

    /**
     * 刪除指定索引的節點
     */
    public E remove(int index) {
        return remove(entry(index));
    }

注意:

  • remove(entry(index))見上邊

  • remove(Object o)須要遍歷鏈表,remove(int index)也須要

 2.六、判斷對象是否存在於LinkedList中(contains(E)

源代碼:

    /**
     * 鏈表中是否包含指定數據o的節點
     */
    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

    /**
     * 從header開始,查找第一個出現o的索引
     */
    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {//從header開始,查找第一個出現null的索引
            for (Entry e = header.next; e != header; e = e.next) {
                if (e.element == null)
                    return index;
                index++;
            }
        } else {
            for (Entry e = header.next; e != header; e = e.next) {
                if (o.equals(e.element))
                    return index;
                index++;
            }
        }
        return -1;
    }

注意:

  • indexOf(Object o)返回第一個出現的元素o的索引

2.七、遍歷LinkedList中的對象(iterator())

使用方式:

        List<String> strList = new LinkedList<String>();
        strList.add("jigang");
        strList.add("nana");
        strList.add("nana2");
        
        Iterator<String> it = strList.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

源代碼:iterator()方法是在父類AbstractSequentialList中實現的,

    public Iterator<E> iterator() {
        return listIterator();
    }

listIterator()方法是在父類AbstractList中實現的,

    public ListIterator<E> listIterator() {
        return listIterator(0);
    }

listIterator(int index)方法是在父類AbstractList中實現的,

    public ListIterator<E> listIterator(final int index) {
        if (index < 0 || index > size())
            throw new IndexOutOfBoundsException("Index: " + index);

        return new ListItr(index);
    }

該方法返回AbstractList的一個內部類ListItr對象

ListItr:

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

上邊這個類並不完整,它繼承了內部類Itr,還擴展了一些其餘方法(eg.向前查找方法hasPrevious()等),至於hasNext()/next()等方法仍是來自於Itr的。

Itr:

    private class Itr implements Iterator<E> {
        
        int cursor = 0;//標記位:標記遍歷到哪個元素
        int expectedModCount = modCount;//標記位:用於判斷是否在遍歷的過程當中,是否發生了add、remove操做

        //檢測對象數組是否還有元素
        public boolean hasNext() {
            return cursor != size();//若是cursor==size,說明已經遍歷完了,上一次遍歷的是最後一個元素
        }

        //獲取元素
        public E next() {
            checkForComodification();//檢測在遍歷的過程當中,是否發生了add、remove操做
            try {
                E next = get(cursor++);
                return next;
            } catch (IndexOutOfBoundsException e) {//捕獲get(cursor++)方法的IndexOutOfBoundsException
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        //檢測在遍歷的過程當中,是否發生了add、remove等操做
        final void checkForComodification() {
            if (modCount != expectedModCount)//發生了add、remove操做,這個咱們能夠查看add等的源代碼,發現會出現modCount++
                throw new ConcurrentModificationException();
        }
    }

注:

  • 上述的Itr我去掉了一個此時用不到的方法和屬性。

  • 這裏的get(int index)方法參照2.3所示。

3、總結

  • LinkedList基於環形雙向鏈表方式實現,無容量的限制

  • 添加元素時不用擴容(直接建立新節點,調整插入節點的先後節點的指針屬性的指向便可)

  • 線程不安全

  • get(int index):須要遍歷鏈表

  • remove(Object o)須要遍歷鏈表

  • remove(int index)須要遍歷鏈表

  • contains(E)須要遍歷鏈表



免費領取驗證碼、內容安全、短信發送、直播點播體驗包及雲服務器等套餐

更多網易技術、產品、運營經驗分享請點擊


相關文章:
【推薦】 使用QUIC

相關文章
相關標籤/搜索