LinkedList源碼分析


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

注:本文jdk源碼版本為jdk1.8.0_172


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的底層數據結構,關聯了前驅節點,后續節點和值。

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

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

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

 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     }

分析:

從源碼上可以非常清楚的了解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日,上午。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM