LinkedList源碼分析

前言:LinkedList的底層數據結構是雙向鏈表,下面具體分析其實現原理。java

注:本文jdk源碼版本爲jdk1.8.0_172node


1..LinkedList介紹

LinkedList繼承於AbstractSequentialList的雙向鏈表,實現List接口,所以也能夠對其進行隊列操做,它也實現了Deque接口,因此LinkedList也可當作雙端隊列使用,還有LinkedList是非同步的。數據結構

1 java.lang.Object 2    ↳     java.util.AbstractCollection<E>
3          ↳     java.util.AbstractList<E>
4                ↳     java.util.AbstractSequentialList<E>
5                      ↳     java.util.LinkedList<E>
6 
7 public class LinkedList<E>
8     extends AbstractSequentialList<E>
9     implements List<E>, Deque<E>, Cloneable, java.io.Serializable {}

因爲LinkedList的底層是雙向鏈表,所以其順序訪問的效率很是高,而隨機訪問的效率就比較低了,由於經過索引去訪問的時候,首先會比較索引值和鏈表長度的1/2,若前者大,則從鏈表尾開始尋找,不然從鏈表頭開始尋找,這樣就把雙向鏈表與索引值聯繫起來了。函數

2.具體源碼分析

LinkedList底層數據結構:源碼分析

 1   private static class Node<E> {  2  E item;  3         Node<E> next;  4         Node<E> prev;  5 
 6         Node(Node<E> prev, E element, Node<E> next) {  7             this.item = element;  8             this.next = next;  9             this.prev = prev; 10  } 11     }

分析:Node爲LinkedList的底層數據結構,關聯了前驅節點,後續節點和值。this

構造函數,LinkedList提供了兩個構造函數:spa

1  public LinkedList() { 2  } 3  public LinkedList(Collection<? extends E> c) { 4         this(); 5  addAll(c); 6     }

add函數,添加元素時,是直接添加在鏈表的結尾:code

 1   public boolean add(E e) {  2  linkLast(e);  3         return true;  4  }  5  void linkLast(E e) {  6         // 取出當前最後一個節點
 7         final Node<E> l = last;  8         // 建立一個新節點,注意其前驅節點爲l,後續節點爲null
 9         final Node<E> newNode = new Node<>(l, e, null); 10         // 記錄新的最後一個節點
11         last = newNode; 12         // 若是最後一個節點爲空,則表示鏈表爲空,則將first節點也賦值爲newNode
13         if (l == null) 14             first = newNode; 15         else
16             // 關聯l的next節點,構成雙向節點
17             l.next = newNode; 18         // 元素總數加1
19         size++; 20         // 修改次數自增
21         modCount++; 22     }

分析:blog

從源碼上能夠很是清楚的瞭解LinkedList加入元素是直接放在鏈表尾的,主要點構成雙向鏈表,總體邏輯並不複雜,經過上述註釋理解應該不成問題。繼承

add(int,element),在具體index上插入元素:

 1 public void add(int index, E element) {  2         // 校驗index是否越界
 3  checkPositionIndex(index);  4         // index和size相同則,添加在鏈表尾
 5         if (index == size)  6  linkLast(element);  7         else
 8             // 在index位置前插入元素
 9  linkBefore(element, node(index)); 10  } 11 // Inserts element e before non-null Node succ.
12 void linkBefore(E e, Node<E> succ) { 13         // assert succ != null;
14         final Node<E> pred = succ.prev; 15         // 建立新的節點 前驅節點爲succ的前驅節點,後續節點爲succ,則e元素就是插入在succ以前的
16         final Node<E> newNode = new Node<>(pred, e, succ); 17         // 構建雙向鏈表,succ的前驅節點爲新節點
18         succ.prev = newNode; 19         // 若是前驅節點爲空,則first爲newNode
20         if (pred == null) 21             first = newNode; 22         else
23             // 構建雙向列表
24             pred.next = newNode; 25         // 元素總數自增
26         size++; 27         // 修改次數自增
28         modCount++; 29     }

分析:該函數並非直接插入鏈表尾,須要進行一個判斷,邏輯並不複雜,經過註釋應該不難理解,但這裏要注意一個函數node(index),取出對應index上的Node元素,下面來具體分析一下。

 1  Node<E> node(int index) {  2         // assert isElementIndex(index);  3         // 由於這裏的x不是next就是prev,當循環停止時,就是對應index上的值  4         // index若是小於鏈表長度的1/2
 5         if (index < (size >> 1)) {  6             Node<E> x = first;  7             // 從鏈表頭開始移動
 8             for (int i = 0; i < index; i++)  9                 x = x.next; 10             return x; 11         } else { 12             // 從鏈表尾開始移動
13             Node<E> x = last; 14             for (int i = size - 1; i > index; i--) 15                 x = x.prev; 16             return x; 17  } 18     }

接下來看構造函數中的addAll方法:

 1 public boolean addAll(int index, Collection<? extends E> c) {  2         // 檢查index是否越界
 3  checkPositionIndex(index);  4 
 5         Object[] a = c.toArray();  6         int numNew = a.length;  7         // 若是插入集合無數據,則直接返回
 8         if (numNew == 0)  9             return false; 10 
11         // succ的前驅節點
12         Node<E> pred, succ; 13         // 若是index與size相同
14         if (index == size) { 15             // succ的前驅節點直接賦值爲最後節點 16             // succ賦值爲null,由於index在鏈表最後
17             succ = null; 18             pred = last; 19         } else { 20             // 取出index上的節點
21             succ = node(index); 22             pred = succ.prev; 23  } 24         // 遍歷插入集合
25         for (Object o : a) { 26             @SuppressWarnings("unchecked") E e = (E) o; 27             // 建立新節點 前驅節點爲succ的前驅節點,後續節點爲null
28             Node<E> newNode = new Node<>(pred, e, null); 29             // succ的前驅節點爲空,則表示succ爲頭,則從新賦值第一個結點
30             if (pred == null) 31                 first = newNode; 32             else
33                 // 構建雙向鏈表
34                 pred.next = newNode; 35             // 將前驅節點移動到新節點上,繼續循環
36             pred = newNode; 37  } 38 
39         // index位置上爲空 賦值last節點爲pred,由於經過上述的循環pred已經走到最後了
40         if (succ == null) { 41             last = pred; 42         } else { 43             // 構建雙向鏈表 44             // 從這裏能夠看出插入集合是在succ[index位置上的節點]以前
45             pred.next = succ; 46             succ.prev = pred; 47  } 48         // 元素總數更新
49         size += numNew; 50         // 修改次數自增
51         modCount++; 52         return true; 53     }

分析:邏輯並不複雜,注意一點便可,插入集合的元素是在index元素以前

其餘重要的源碼分析:

 1 // 經過index獲取元素
 2 public E get(int index) {  3     // 檢查index是否越界
 4  checkElementIndex(index);  5     // 經過node函數返回節點值 node函數前面已經分析過
 6     return node(index).item;  7 }  8 
 9 // 增長元素在鏈表頭位置
10 private void linkFirst(E e) { 11     final Node<E> f = first; 12     // 建立新節點 前驅節點爲null,後續節點爲first節點
13     final Node<E> newNode = new Node<>(null, e, f); 14     // 更新first節點
15     first = newNode; 16     // 若是f爲空,表示原來爲空,更新last節點爲新節點
17     if (f == null) 18         last = newNode; 19     else
20         // 構建雙向鏈表
21         f.prev = newNode; 22     // 元素總數自增
23     size++; 24     // 修改次數自增
25     modCount++; 26 } 27     
28  // 釋放頭節點
29 private E unlinkFirst(Node<E> f) { 30     // assert f == first && f != null;
31     final E element = f.item; 32     final Node<E> next = f.next; 33     f.item = null; 34     f.next = null; // help GC 35     // 更新頭節點
36     first = next; 37     if (next == null) 38         last = null; 39     else
40         // 將頭節點的前驅節點賦值爲null
41         next.prev = null; 42     // 元素總數自減
43     size--; 44     // 修改次數自增
45     modCount++; 46     // 返回刪除的節點數據
47     return element; 48 } 49  // 釋放尾節點
50 private E unlinkLast(Node<E> l) { 51     // assert l == last && l != null;
52     final E element = l.item; 53     // 和釋放頭節點相反,這裏取出前驅節點,其餘邏輯同樣
54     final Node<E> prev = l.prev; 55     l.item = null; 56     l.prev = null; // help GC
57     last = prev; 58     if (prev == null) 59         first = null; 60     else
61         prev.next = null; 62     size--; 63     modCount++; 64     return element; 65 }

3.總結

總體分析下來,其實LinkedList仍是比較簡單的,上面對一些重要的相關源碼進行了分析,主要重點以下:

#1.LinkedList底層數據結構爲雙向鏈表,非同步。

#2.LinkedList容許null值。

#3.因爲雙向鏈表,順序訪問效率高,而隨機訪問效率較低。

#4.注意源碼中的相關操做,主要是構建雙向鏈表。


by Shawn Chen,2019.09.02日,上午。

相關文章
相關標籤/搜索