給jdk寫註釋系列之jdk1.6容器(2)-LinkedList源碼解析

LinkedList是基於鏈表結構的一種List,在分析LinkedList源碼前有必要對鏈表結構進行說明。
 
1.鏈表的概念
     鏈表是由一系列非連續的節點組成的存儲結構,簡單分下類的話,鏈表又分爲單向鏈表和雙向鏈表,而單向/雙向鏈表又能夠分爲循環鏈表和非循環鏈表,下面簡單就這四種鏈表進行圖解說明。
    
     1.1.單向鏈表
          單向鏈表就是經過每一個結點的指針指向下一個結點從而連接起來的結構,最後一個節點的next指向null。
 
          
 
     1. 2.單向循環鏈表
          單向循環鏈表和單向列表的不一樣是,最後一個節點的next不是指向null,而是指向head節點,造成一個「環」。
 
          
 
     1. 3.雙向鏈表
          從名字就能夠看出,雙向鏈表是包含兩個指針的,pre指向前一個節點,next指向後一個節點,可是第一個節點head的pre指向null,最後一個節點的tail指向null。
 
          
 
     1. 4.雙向循環鏈表
          雙向循環鏈表和雙向鏈表的不一樣在於,第一個節點的pre指向最後一個節點,最後一個節點的next指向第一個節點,也造成一個「環」。 而LinkedList就是基於雙向循環鏈表設計的
 
          
 
         更形象的解釋下就是:雙向循環鏈表就像一羣小孩手牽手圍成一個圈,第一個小孩的右手拉着第二個小孩的左手,第二個小孩的左手拉着第一個小孩的右手。。。最後一個小孩的右手拉着第一個小孩的左手。
 
  ok,鏈表的概念介紹完了,下面進入寫註釋和源碼分析部分,可是在這以前仍是要提醒一句,不是囉嗦哦,鏈表操做理解起來比數組困難了很多,因此務必要理解上面的圖解,若是源碼解析過程當中遇到理解困難,請返回來照圖理解。
 
2.定義    
 
  一樣先來看看 LinkedList 的定義部分,
1 public class LinkedList<E>
2     extends AbstractSequentialList<E>
3     implements List<E>, Deque<E>, Cloneable, java.io.Serializable

     能夠看出LinkedList 繼承AbstractSequentialList 抽象類,實現了List,Deque,CloneableSerializable 幾個接口,AbstractSequentialList 繼承 AbstractList,是對其中方法的再抽象,其主要做用是最大限度地減小了實現受「連續訪問」數據存儲(如連接列表)支持的此接口所需的工做,簡單說就是,若是須要快速的添加刪除數據等,用AbstractSequentialList抽象類,如果須要快速隨機的訪問數據等用AbstractList抽象類(詳細說明會在iterator 分析中進行解釋)。html

     Deque 是一個雙向隊列,也就是既能夠先入先出,又能夠先入後出,再直白一點就是既能夠在頭部添加元素又在尾部添加元素,既能夠在頭部獲取元素又能夠在尾部獲取元素。看下Deque的定義
 1 public interface Deque<E> extends Queue<E> {
 2     void addFirst(E e);
 3     boolean offerFirst(E e);
 4     boolean offerLast(E e);
 5     E removeFirst();
 6     E removeLast();
 7     E pollFirst();
 8     E pollLast();
 9     E getFirst();
10     E getLast();
11     E peekFirst();
12     E peekLast();
13     boolean removeFirstOccurrence(Object o);
14     boolean removeLastOccurrence(Object o);
15     // *** Queue methods ***
16     boolean add(E e);
17     boolean offer(E e);
18     E remove();
19     E poll();
20     E element();
21     E peek();
22     // *** Stack methods ***
23     void push(E e);
24     E pop();
25    // *** Collection methods ***
26     boolean remove(Object o);
27     boolean contains(Object o);
28     public int size();
29     Iterator<E> iterator();
30     Iterator<E> descendingIterator();
31 }

 

3.底層存儲java

 

     明白了上面的鏈表概念,以及LinkedList是基於雙向循環鏈表設計的,下面在具體來看看LinkedList的底層存儲實現方式。
1 private transient Entry<E> header = new Entry<E>(null, null, null);
2 private transient int size = 0;

     LinkedList中提供了上面兩個屬性,其中size和ArrayList中同樣用來計數,表示list的元素數量,而header則是鏈表的頭結點,Entry則是鏈表的節點對象。設計模式

 1 private static class Entry<E> {
 2        E element;  // 當前存儲元素
 3        Entry<E> next;  // 下一個元素節點
 4        Entry<E> previous;  // 上一個元素節點
 5  
 6        Entry(E element, Entry<E> next, Entry<E> previous) {
 7            this.element = element;
 8            this.next = next;
 9            this.previous = previous;
10        }
11 }
    Entry爲 LinkedList 的內部類,其中定義了當前存儲的元素,以及該元素的上一個元素和下一個元素。結合上面雙向鏈表的示意圖很容易看懂。
 
4.構造方法
 1   /**
 2      * 構造一個空的LinkedList .
 3      */
 4     public LinkedList() {
 5         //將header節點的前一節點和後一節點都設置爲自身
 6         header.next = header. previous = header ;
 7     }
 8  
 9     /**
10      * 構造一個包含指定 collection 中的元素的列表,這些元素按其 collection 的迭代器返回的順序排列
11      */
12     public LinkedList(Collection<? extends E> c) {
13         this();
14        addAll(c);
15     }
    須要注意的是空的 LinkedList構造方法,它將header節點的前一節點和後一節點都設置爲自身,這裏便說明 LinkedList 是一個雙向循環鏈表,若是隻是單存的雙向鏈表而不是循環鏈表,他的實現應該是這樣的:
1 public LinkedList() {
2        header.next = null;
3        header. previous = null;
4 }
     非循環鏈表的狀況應該是header節點的前一節點和後一節點均爲null(參見鏈表圖解)。
 
5.增長
 
     增長方法的代碼讀起來比較不容易理解,須要的時候請結合鏈表圖解。
 1     /**
 2      * 將一個元素添加至list尾部
 3      */
 4     public boolean add(E e) {
 5        // 在header前添加元素e,header前就是最後一個結點啦,就是在最後一個結點的後面添加元素e
 6        addBefore(e, header);
 7         return true;
 8     }
 9     /**
10      * 在指定位置添加元素
11      */
12     public void add(int index, E element) {
13         // 若是index等於list元素個數,則在隊尾添加元素(header以前),不然在index節點前添加元素
14         addBefore(element, (index== size ? header : entry(index)));
15     }
16      
17    private Entry<E> addBefore(E e, Entry<E> entry) {
18        // 用entry建立一個要添加的新節點,next爲entry,previous爲entry.previous,意思就是新節點插入entry前面,肯定自身的先後引用,
19        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
20         // 下面修改newEntry的先後節點的引用,確保其鏈表的引用關係是正確的
21        // 將上一個節點的next指向本身
22        newEntry. previous.next = newEntry;
23        // 將下一個節點的previous指向本身
24        newEntry. next.previous = newEntry;
25        // 計數+1
26         size++;
27         modCount++;
28         return newEntry;
29   }
     到這裏能夠發現一點疑慮, header做爲雙向循環鏈表的頭結點是不保存數據的,也就是說hedaer中的element永遠等於null
    
 1     /**
 2      * 添加一個集合元素到list中
 3      */
 4     public boolean addAll(Collection<? extends E> c) {
 5             // 將集合元素添加到list最後的尾部
 6         return addAll(size , c);
 7     }
 8  
 9     /**
10      * 在指定位置添加一個集合元素到list中
11      */
12     public boolean addAll(int index, Collection<? extends E> c) {
13         // 越界檢查
14         if (index < 0 || index > size)
15             throw new IndexOutOfBoundsException( "Index: "+index+
16                                                 ", Size: "+size );
17         Object[] a = c.toArray();
18         // 要插入元素的個數
19         int numNew = a.length ;
20         if (numNew==0)
21             return false;
22         modCount++;
23  
24         // 找出要插入元素的先後節點
25         // 獲取要插入index位置的下一個節點,若是index正好是lsit尾部的位置那麼下一個節點就是header,不然須要查找index位置的節點
26         Entry<E> successor = (index== size ? header : entry(index));
27         // 獲取要插入index位置的上一個節點,由於是插入,因此上一個點擊就是未插入前下一個節點的上一個
28         Entry<E> predecessor = successor. previous;
29         // 循環插入
30         for (int i=0; i<numNew; i++) {
31             // 構造一個節點,確認自身的先後引用
32             Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
33             // 將插入位置上一個節點的下一個元素引用指向當前元素(這裏不修改下一個節點的上一個元素引用,是由於下一個節點隨着循環一直在變)
34             predecessor. next = e;
35             // 最後修改插入位置的上一個節點爲自身,這裏主要是爲了下次遍歷後續元素插入在當前節點的後面,確保這些元素自己的順序
36             predecessor = e;
37         }
38         // 遍歷完全部元素,最後修改下一個節點的上一個元素引用爲遍歷的最後一個元素
39         successor. previous = predecessor;
40  
41         // 修改計數器
42         size += numNew;
43         return true;
44     }

 

 
     增長方法的代碼理解起來可能有些困難,可是隻要理解了雙向鏈表的存儲結構,掌握增長的核心邏輯就能夠了,這裏總結一下往鏈表中增長元素的核心邏輯:1.將元素轉換爲鏈表節點,2.增長該節點的先後引用(即pre和next分別指向哪個節點),3.先後節點對該節點的引用(前節點的next指向該節點,後節點的pre指向該節點)。如今再看下就這麼簡單麼,就是 改變先後的互相指向關係(看圖增長元素先後的變化)。
     其實刪除也是同樣的對不對?下面看看刪除方法的實現。
 
     PS:不要問我entry()方法是怎麼回事,這裏先不講,打我也不講。。。
 
6.刪除
 1     /**
 2      * 刪除第一個匹配的指定元素
 3      */
 4     public boolean remove(Object o) {
 5          // 遍歷鏈表找到要被刪除的節點
 6         if (o==null) {
 7             for (Entry<E> e = header .next; e != header; e = e.next ) {
 8                 if (e.element ==null) {
 9                     remove(e);
10                     return true;
11                 }
12             }
13         } else {
14             for (Entry<E> e = header .next; e != header; e = e.next ) {
15                 if (o.equals(e.element )) {
16                     remove(e);
17                     return true;
18                 }
19             }
20         }
21         return false;
22     }
23  
24     private E remove(Entry<E> e) {
25         if (e == header )
26            throw new NoSuchElementException();
27  
28        // 被刪除的元素,供返回
29         E result = e. element;
30        // 下面修正先後對該節點的引用
31        // 將該節點的上一個節點的next指向該節點的下一個節點
32        e. previous.next = e.next;
33        // 將該節點的下一個節點的previous指向該節點的上一個節點
34        e. next.previous = e.previous;
35        // 修正該節點自身的先後引用
36         e. next = e.previous = null;
37        // 將自身置空,讓gc能夠儘快回收
38         e. element = null;
39        // 計數器減一
40         size--;
41         modCount++;
42         return result;
43     }

     上面對於鏈表增長元素總結了,一句話就是「改變先後的互相指向關係」,刪除也是一樣的道理,因爲節點被刪除,該節點的上一個節點和下一個節點互相拉一下小手就能夠了,注意的是「互相」,不能一廂情願。數組

     
7.修改
 1     /**
 2      * 修改指定位置索引位置的元素
 3      */
 4     public E set( int index, E element) {
 5         // 查找index位置的節點
 6         Entry<E> e = entry(index);
 7         // 取出該節點的元素,供返回使用
 8         E oldVal = e. element;
 9         // 用新元素替換舊元素
10         e. element = element;
11         // 返回舊元素
12         return oldVal;
13     }    
     set方法看起來簡單了不少,只要修改該節點上的元素就行了,可是不要忽略了這裏的entry()方法,重點就是它。
 
8.查詢
 
     終於到查詢了,終於發現了上面常常出現的那個方法entry()根據index查詢節點,咱們知道數組是有下標的,經過下標操做自然的支持根據index查詢元素,而鏈表中是沒有index概念呢,那麼怎麼樣才能經過index查詢到對應的元素呢,下面就來看看LinkedList是怎麼實現的。
 1     /**
 2      * 查找指定索引位置的元素
 3      */
 4     public E get( int index) {
 5         return entry(index).element ;
 6     }
 7  
 8     /**
 9      * 返回指定索引位置的節點
10      */
11     private Entry<E> entry( int index) {
12         // 越界檢查
13         if (index < 0 || index >= size)
14             throw new IndexOutOfBoundsException( "Index: "+index+
15                                                 ", Size: "+size );
16         // 取出頭結點
17         Entry<E> e = header;
18         // size>>1右移一位表明除以2,這裏使用簡單的二分方法,判斷index與list的中間位置的距離
19         if (index < (size >> 1)) {
20             // 若是index距離list中間位置較近,則從頭部向後遍歷(next)
21             for (int i = 0; i <= index; i++)
22                 e = e. next;
23         } else {
24             // 若是index距離list中間位置較遠,則從頭部向前遍歷(previous)
25             for (int i = size; i > index; i--)
26                 e = e. previous;
27         }
28         return e;
29     }
     如今知道了,LinkedList是經過從header開始index計爲0,而後一直往下遍歷(next),直到到底index位置。爲了優化查詢效率,LinkedList採用了二分查找(這裏說的二分只是簡單的一次二分),判斷index與size中間位置的距離,採起從header向後仍是向前查找。
      到這裏咱們明白,基於雙向循環鏈表實現的LinkedList,經過索引Index的操做時低效的,index所對應的元素越靠近中間所費時間越長。而向鏈表兩端插入和刪除元素則是很是高效的(若是不是兩端的話,都須要對鏈表進行遍歷查找)。
 
9.是否包含     
 1   /**
 2      * Returns <tt>true</tt> if this list contains the specified element.
 3      * More formally, returns <tt>true</tt> if and only if this list contains
 4      * at least one element <tt>e</tt> such that
 5      * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
 6      *
 7      * @param o element whose presence in this list is to be tested
 8      * @return <tt> true</tt> if this list contains the specified element
 9      */
10     public boolean contains(Object o) {
11         return indexOf(o) != -1;
12     }
13    
14     /**
15      * Returns the index of the first occurrence of the specified element
16      * in this list, or -1 if this list does not contain the element.
17      * More formally, returns the lowest index <tt>i</tt> such that
18      * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
19      * or -1 if there is no such index.
20      *
21      * @param o element to search for
22      * @return the index of the first occurrence of the specified element in
23      *         this list, or -1 if this list does not contain the element
24      */
25     public int indexOf(Object o) {
26         int index = 0;
27         if (o==null) {
28             for (Entry e = header .next; e != header; e = e.next ) {
29                 if (e.element ==null)
30                     return index;
31                 index++;
32             }
33         } else {
34             for (Entry e = header .next; e != header; e = e.next ) {
35                 if (o.equals(e.element ))
36                     return index;
37                 index++;
38             }
39         }
40         return -1;
41     }
42  
43     /**
44      * Returns the index of the last occurrence of the specified element
45      * in this list, or -1 if this list does not contain the element.
46      * More formally, returns the highest index <tt>i</tt> such that
47      * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
48      * or -1 if there is no such index.
49      *
50      * @param o element to search for
51      * @return the index of the last occurrence of the specified element in
52      *         this list, or -1 if this list does not contain the element
53      */
54     public int lastIndexOf(Object o) {
55         int index = size ;
56         if (o==null) {
57             for (Entry e = header .previous; e != header; e = e.previous ) {
58                 index--;
59                 if (e.element ==null)
60                     return index;
61             }
62         } else {
63             for (Entry e = header .previous; e != header; e = e.previous ) {
64                 index--;
65                 if (o.equals(e.element ))
66                     return index;
67             }
68         }
69         return -1;
70     }
     和 public  boolean  remove(Object o) 同樣,indexOf查詢元素位於容器的索引位置,都是須要對鏈表進行遍歷操做,固然也就是低效了啦。
 
10.判斷容量
 1   /**
 2      * Returns the number of elements in this list.
 3      *
 4      * @return the number of elements in this list
 5      */
 6     public int size() {
 7         return size ;
 8     }
 9  
10     /**
11      * {@inheritDoc}
12      *
13      * <p>This implementation returns <tt>size() == 0 </tt>.
14      */
15     public boolean isEmpty() {
16         return size() == 0;
17     }
     和ArrayList同樣,基於計數器size操做,容量判斷很方便。
 
     到這裏LinkedList就分析完了,不對好像還差些什麼對不對?是什麼呢,就是最開始說的Deque雙端隊列,明白了鏈表原理和LinkedList的基本crud操做,Deque的LinkedList實現就已是so easy了,咱們簡單看下。
 
11.LinkedList實現的Deque雙端隊列     
 1     /**
 2      * Adds the specified element as the tail (last element) of this list.
 3      *
 4      * @param e the element to add
 5      * @return <tt> true</tt> (as specified by {@link Queue#offer})
 6      * @since 1.5
 7      */
 8     public boolean offer(E e) {
 9         return add(e);
10     }
11  
12     /**
13      * Retrieves and removes the head (first element) of this list
14      * @return the head of this list, or <tt>null </tt> if this list is empty
15      * @since 1.5
16      */
17     public E poll() {
18         if (size ==0)
19             return null;
20         return removeFirst();
21     }
22  
23     /**
24      * Removes and returns the first element from this list.
25      *
26      * @return the first element from this list
27      * @throws NoSuchElementException if this list is empty
28      */
29     public E removeFirst() {
30         return remove(header .next);
31     }
32  
33     /**
34      * Retrieves, but does not remove, the head (first element) of this list.
35      * @return the head of this list, or <tt>null </tt> if this list is empty
36      * @since 1.5
37      */
38     public E peek() {
39         if (size ==0)
40             return null;
41         return getFirst();
42     }
43  
44     /**
45      * Returns the first element in this list.
46      *
47      * @return the first element in this list
48      * @throws NoSuchElementException if this list is empty
49      */
50     public E getFirst() {
51         if (size ==0)
52            throw new NoSuchElementException();
53  
54         return header .next. element;
55     }
56  
57     /**
58      * Pushes an element onto the stack represented by this list.  In other
59      * words, inserts the element at the front of this list.
60      *
61      * <p>This method is equivalent to {@link #addFirst}.
62      *
63      * @param e the element to push
64      * @since 1.6
65      */
66     public void push(E e) {
67         addFirst(e);
68     }
69  
70     /**
71      * Inserts the specified element at the beginning of this list.
72      *
73      * @param e the element to add
74      */
75     public void addFirst(E e) {
76        addBefore(e, header.next );
77     }
     看看Deque 的實現是否是很簡單,邏輯都是基於上面講的鏈表操做的,對於隊列的一些概念我不打算在這裏講,是由於後面隊列會單獨拿出來分析啦,這裏只要理解基於鏈表實現的list內部是怎麼操做的就能夠啦。。。
 
     LinkedList 完!
 
相關文章
相關標籤/搜索