LinkedList源碼解析

LinkedList特點總結

  • LinkedList實現List接口,使用雙向鏈表實現,元素可以是null
  • 可以被當作堆棧,隊列,雙端隊列使用
  • 因其使用鏈表實現,查詢需要遍歷O(n)時間復雜度,插入時不再需要復制移動元素O(1)時間復雜度
  • 類中的iterator()方法和listIterator()方法返回的iterators迭代器是fail-fast的:當某一個線程A通過iterator去遍歷某集合的過程中,若該集合的內容被其他線程所改變了;那么線程A訪問集合時,就會拋出ConcurrentModificationException異常,產生fail-fast事件
  • 和ArrayList一樣,不是線程安全的,可以使用Collections.synchronizedList()變成支持并發的容器

LinkedList是由一個雙向鏈表來維護的,對于增刪改查元素理解最清晰地理解就是畫一張圖

源碼分析

考慮到之前是直接拷貝jdk源碼在源碼上通過注釋的方式進行解讀,這樣看起來會比較雜亂,所以這次采用分塊解讀的方式

  1. 節點類

因為采用鏈表的方式實現,所以需要先定義一個結點類,源碼中使用靜態內部類的方式定義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類,next指向其下一個Node,prev指向其上一個Node

  1. LinkedList的成員變量

    transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;

size是當前List中的元素個數,first和last都是Node類型的變量,分別指向鏈表的第一個節點和最后一個節點,這三個變量都是transient,說明不希望進行序列化

  1. 構造方法
    public LinkedList() {
    }

    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

第一個構造方法創建了一個空的LinkedList,第二個構造方法是通過一個Collection接口的實現類對象進行初始化,首先調用第一個構造方法創建一個空的LinkedList然后將參數指向的Collection中的元素添加到LinkedList中,元素的順序是Collection的iterator返回的順序

  1. 增加元素

LinkedList提供了頭插入addFirst(E e)、尾插入addLast(E e)、add(E e)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、add(int index, E element)這些添加元素的方法

源碼中提供了幾種增加元素的方法

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

首先看add方法,add方法中調用了linkLast方法,可以看出來是在list的末尾添上一個Node,這個方法與源碼中的addLast方法效果是相同的,只是add方法是有返回的,凡是返回比較雞肋

   public void addLast(E e) {
        linkLast(e);
    }

順藤摸瓜,我們來看一個linkLast方法

   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++;
    }
  • 首先通過last指針找到list的最后一個node,然后創建一個新的node,這個newNode的next指向null,prev指向鏈表的最后一個節點
  • 將鏈表的last指向創建的newNode
  • 判斷之前找到的last node 如果該node是null說明原來list中沒有元素,將創建的newNode賦值給first表示新node是最后一個元素也是第一個元素,如果l不是null,表示原來的list中有元素,此時將l的next指向newNode
  • 最后需要將size++ 表示list元素多了一個

除了在list的尾巴上添加元素,jdk源碼中還提供了在鏈表頭上添加元素的方法

   public void addFirst(E e) {
       linkFirst(e);
   }
  
   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++;
   }

addFirst會直接調用linkFirst完成插入操作,和上面linkLast方法其實是相仿的

  • 首先找到first指向的元素,有可能是一個null
  • 創建一個新的node,其prev指向null,因為newNode是鏈表的第一個元素
  • first指向newNode
  • 如果f指向null,表示鏈表原先是沒有元素的,那么newNode就是當前唯一的元素,last也要指向newNode,如果f不是null,表示原先鏈表中就有元素,需要將f指向的(此時是鏈表的第二個元素)的prev指向newNode

LinkedList同時也提供了指定index插入元素

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

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

    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

  • 首先進行index的檢查,保證index在[0,size]之間,如果是非法的index就拋出一個異常
  • 如果index==size 就是在鏈表的最后進行添加
  • 否則調用linkBefore方法,完成元素添加
   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;
       }
   }

node(int index)這個方法返回在鏈表index位置上的node,返回的node是一個不為null的node,// assert isElementIndex(index);被注釋掉是因為前面已經進行index == size 判斷就不再這邊進行判斷了,此外需要注意的是這邊使用了一個小技巧

  • 如果index屬于鏈表的前面一半 index < (size >> 1) 就從first指向的元素進行遍歷
  • 如果index屬于鏈表的后面一半元素 就從last指向的元素進行遍歷
  • 返回node
    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++;
    }

linkBefore方法是最終完成插入操作的方法,同樣succ!=null也被注釋掉了,因為上面node方法返回的是一個不為null的node,linkBefore其實是一個雙向鏈表的插入操作

  • 首先找到succ這個node prev指針指向的node,即succ左手邊的一個node
  • 創建一個新的node,這個newNode prev指向pred,next指向succ
  • 因為jdk中在add(index,e)中僅對index==size的情況做了特殊處理,并沒有對index==0的時候做出特殊處理,即在第一個位置進行插入,此時pred會是null,當pred == null時表示index == 0在第一個位置進行插入了,pred右手邊指針需要指向newNode,其他情況下pred.next指向newNode即可
  1. 訪問元素

LinkedList提供了getFirst()、getLast()、contains(Object o)、get(int index)、indexOf(Object o)、lastIndexOf(Object o)這些查找元素的方法

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

    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

首先進行index檢查,然后通過node(index)獲取index下的元素,最后通過node.item獲取到值,需要注意index檢查的時候與add index檢查的方法存在一點點細微的區別:index >= 0 && index < size;這里index必須在[0,size-1]

jdk還提供了一些取特殊位置元素的方法

    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }
  1. 刪除元素

LinkedList提供了頭刪除removeFirst()、尾刪除removeLast()、remove(int index)、remove(Object o)、clear()這些刪除元素的方法

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

首先index檢查與上面get中的方式相同,然后調用找到index指向的元素,調用unlink完成刪除

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

因為node(index)方法返回一個非null的node,所以這邊把 x != null注釋掉了,unlink是一個雙向鏈表刪除的操作

  • 首先獲取x的值,值作為最后的返回
  • 分別獲取x next和prev指向的元素
  • 如果prev==null 表示要刪除的是第一個元素,同樣,如果next == null表示要刪除的是最后一個元素
  • 如果prev==null 將first指向鏈表的第二個元素即next指向的元素,否則prev.next = next;x.prev = null;
  • 如果next==null 表示要刪除的是最后一個元素,last = prev;表示將x前面的一個元素作為鏈表的最后一個元素,否則 next.prev = prev;x.next = null;
  • 最后x.item = null size-- modCount++
    最好的方式還是畫一張圖幫助理解

同樣的,jdk也提供了一些在特殊位置刪除元素的方法

    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
        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;
    }

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

不詳細展開了,需要注意的是比如刪除第一個節點的時候需要注意鏈表只有一個元素的情況

  1. 修改元素的值
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

需要注意最后返回原先的值

  1. 其他常見操作
    public int size() {
        return size;
    }

返回當前鏈表中元素的個數

   public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

    public int indexOf(Object o) {
        int index = 0;
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }

    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }

返回元素o在鏈表中的index,準確說應該是第一個o的index,與ArrayList中處理的方式是一樣的,遍歷的時候對null和non-null做了判斷

  1. Queue操作

Queue操作提供了peek()、element()、poll()、remove()、offer(E e)這些方法

    //獲取但不移除此隊列的頭;如果此隊列為空,則返回 null
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

    //獲取但不移除此隊列的頭;如果此隊列為空,則拋出NoSuchElementException異常
    public E element() {
        return getFirst();
    }

    //獲取并移除此隊列的頭,如果此隊列為空,則返回 null
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    //獲取并移除此隊列的頭,如果此隊列為空,則拋出NoSuchElementException異常
    public E remove() {
        return removeFirst();
    }

    //將指定的元素值(E e)插入此列表末尾
    public boolean offer(E e) {
        return add(e);
    }

Deque操作提供了offerFirst(E e)、offerLast(E e)、peekFirst()、peekLast()、pollFirst()、pollLast()、push(E e)、pop()、removeFirstOccurrence(Object o)、removeLastOccurrence(Object o)這些方法

 //獲取但不移除此隊列的頭;如果此隊列為空,則返回 null
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

    //獲取但不移除此隊列的頭;如果此隊列為空,則拋出NoSuchElementException異常
    public E element() {
        return getFirst();
    }

    //獲取并移除此隊列的頭,如果此隊列為空,則返回 null
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    //獲取并移除此隊列的頭,如果此隊列為空,則拋出NoSuchElementException異常
    public E remove() {
        return removeFirst();
    }

    //將指定的元素值(E e)插入此列表末尾
    public boolean offer(E e) {
        return add(e);
    }

    // Deque operations

    //將指定的元素插入此雙端隊列的開頭
    public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }

    //將指定的元素插入此雙端隊列的末尾
    public boolean offerLast(E e) {
        addLast(e);
        return true;
    }

    //獲取,但不移除此雙端隊列的第一個元素;如果此雙端隊列為空,則返回 null
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }

    //獲取,但不移除此雙端隊列的最后一個元素;如果此雙端隊列為空,則返回 null
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

    //獲取并移除此雙端隊列的第一個元素;如果此雙端隊列為空,則返回 null
    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

    //獲取并移除此雙端隊列的最后一個元素;如果此雙端隊列為空,則返回 null
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

    //將一個元素推入此雙端隊列所表示的堆棧(換句話說,此雙端隊列的頭部)
    public void push(E e) {
        addFirst(e);
    }

    //從此雙端隊列所表示的堆棧中彈出一個元素(換句話說,移除并返回此雙端隊列的頭部)
    public E pop() {
        return removeFirst();
    }

    //從此雙端隊列移除第一次出現的指定元素,如果列表中不包含次元素,則沒有任何改變
    public boolean removeFirstOccurrence(Object o) {
        return remove(o);
    }

    //從此雙端隊列移除最后一次出現的指定元素,如果列表中不包含次元素,則沒有任何改變
    public boolean removeLastOccurrence(Object o) {
        //由于LinkedList中允許存放null,因此下面通過兩種情況來分別處理
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) { //逆向向前
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
  1. 遍歷方式
  • 使用迭代器iterator遍歷
Iterator iter = list.iterator();
   while (iter.hasNext())
   {
       System.out.println(iter.next());
   }    
  • 使用listIterator遍歷
ListIterator<String> lIter = list.listIterator();
  //順向遍歷
  while(lIter.hasNext()){
      System.out.println(lIter.next());
  }
  //逆向遍歷
  while(lIter.hasPrevious()){
      System.out.println(lIter.previous());
  }
  • 使用foreach遍歷
 for(String str:list)
    {
        System.out.println(str);
    }    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容