Java集合詳解(三):LinkedList原理解析

25minutes發表於2021-09-09

概述

本文是基於jdk8_271原始碼進行分析的。

LinkedList底層是基於連結串列實現。連結串列沒有長度限制,記憶體地址不需要固定長度,也不需要是連續的地址來進行儲存,只需要透過引用來關聯前後元素即可完成整個連結串列的連續。所以連結串列的優點就是新增刪除元素比較快,只需要移動指標,並且不需要判斷擴容。缺點就是因為沒有索引,所以在查詢和遍歷元素時候比較慢。

使用場景:在增刪操作使用較多,查詢遍歷操作使用較少情況下比較適合去使用;例如:拿來當棧使用。

資料結構

  • 繼承實現關係

1 public class LinkedList
2 extends AbstractSequentialList
3 implements List, Deque, Cloneable, java.io.Serializable

  1. AbstractSequentialList :本質上面與繼承 AbstractList 沒有什麼區別,AbstractSequentialList 完善了 AbstractList 中沒有實現的方法。
  2. List:實現List介面。
  3. Deque:實現Deque佇列介面,擁有作為雙端佇列(佇列和棧)的功能。
  4. Cloneable:重寫clone()方法,透過建立新的LinkedList 物件,遍歷複製資料進行物件複製。
  5. Serializable:重寫read/writeObject() 方法實現序列化。
  • 靜態內部類

為什麼Node這個類是靜態的?答案是:這跟記憶體洩露有關,Node類是在LinkedList類中的,也就是一個內部類,若不使用static修飾,那麼Node就是一個普通的內部類,在java中,一個普通內部類在例項化之後,預設會持有外部類的引用,這就有可能造成記憶體洩露(內部類與外部類生命週期不一致時)。但使用static修飾過的內部類(稱為靜態內部類),就不會有這種問題。

非靜態內部類會自動生成一個構造器依賴於外部類:也是內部類可以訪問外部類的例項變數的原因。

靜態內部類不會生成,訪問不了外部類的例項變數,只能訪問類變數。

 1     private static class Node { 2         E item;
 3         Node next;    // 後繼節點
 4         Node prev;    // 前置節點
 5 
 6         Node(Node prev, E element, Node next) { 7             this.item = element; 8             this.next = next; 9             this.prev = prev; 10 } 11     }
  • 基本屬性

1 // 元素數量
2 transient int size = 0; 3 // 頭結點
4 transient Node first; 5 // 尾節點
6 transient Node last;

  • 構造方法

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

主要方法解析

  • 獲取元素

peek():佇列的查,獲取隊頭元素。

 1     public E get(int index) {    // 根據索引獲取
 2         checkElementIndex(index);
 3         return node(index).item; 4     }
 5     Node node(int index) { 6         // assert isElementIndex(index); 7         // 利用二分法查詢;小於中間數從頭結點開始找,否則從尾節點開始找
 8         if (index > 1)) {
 9             Node x = first; 10             for (int i = 0; i  x = last; 15             for (int i = size - 1; i > index; i--) 16                 x = x.prev; 17             return x; 18 } 19 } 20     // 獲取隊頭元素 ,但是不刪除佇列的頭元素(雙端佇列Deque中的方法)
21     public E getFirst() { 22         final Node f = first; 23         if (f == null) 24             throw new NoSuchElementException(); 25         return f.item; 26 } 27     // 獲取隊尾元素,但是不刪除佇列的尾元素(實現雙端佇列Deque中的方法)
28     public E getLast() { 29         final Node l = last; 30         if (l == null) 31             throw new NoSuchElementException(); 32         return l.item; 33 } 34 
35     // 佇列的查。獲取隊頭元素。
36     public E peek() { 37         final Node f = first; 38         return (f == null) ? null : f.item; 39     }
  • 新增元素

offer():佇列的增,新增隊尾元素,底層實現是呼叫add()->linkLast()。

push():棧的增,把元素壓入棧中,新增對頭元素,底層實現是呼叫addFirst()->linkFirst()。

 1     // 新增元素,預設在連結串列尾部新增
 2     public boolean add(E e) { 3         linkLast(e);
 4         return true;
 5     }
 6     // 指定索引新增元素
 7     public void add(int index, E element) { 8         checkPositionIndex(index);  // 檢驗下標是否越界
 9 
10         if (index == size)  // 如果要插入的索引等於現有元素長度,說明是要在尾部插入元素
11 linkLast(element); 12         else    // 否則,獲取該索引節點,在該節點之前插入元素
13 linkBefore(element, node(index)); 14 } 15 
16     public boolean addAll(Collection extends E> c) { 17         return addAll(size, c); 18 } 19     public boolean addAll(int index, Collection extends E> c) { 20         checkPositionIndex(index);  // 檢驗下標是否越界
21 
22         Object[] a = c.toArray(); 23         int numNew = a.length; 24         if (numNew == 0) 25             return false; 26 
27         // pred:前置節點,在該節點之後插入元素。succ:該索引位節點。
28         Node pred, succ; 29         if (index == size) { 30             succ = null; 31             pred = last; 32         } else { 33             succ = node(index); 34             pred = succ.prev; 35 } 36         // 將陣列設定為連結串列
37         for (Object o : a) { 38             @SuppressWarnings("unchecked") E e = (E) o; 39             Node newNode = new Node(pred, e, null);    // 新建一個節點,指向前置節點
40             if (pred == null)   // 如果前置節點為空,說明該連結串列為空。將頭節點指向當前節點
41                 first = newNode; 42             else    // 前置節點的後繼節點指向當前節點
43                 pred.next = newNode; 44             pred = newNode; // 將當前節點設定為前置節點,供後面需要插入的節點使用
45 } 46 
47         if (succ == null) { 48             last = pred; 49         } else { 50             pred.next = succ; 51             succ.prev = pred; 52 } 53 
54         size += numNew; 55         modCount++; 56         return true; 57 } 58 
59     // 新增元素到頭結點(實現雙端佇列Deque中的方法)
60     public void addFirst(E e) { 61 linkFirst(e); 62 } 63     private void linkFirst(E e) { 64         final Node f = first;    // 原頭節點
65         final Node newNode = new Node(null, e, f); // 新建一個節點,要新增的元素
66         first = newNode;    // 將頭結點指向該新建的節點
67         if (f == null)  // 如果原頭結點為空,說明原連結串列為空。這是新增的第一個元素,將尾結點也指向該新建的節點
68             last = newNode; 69         else    // 如果原頭結點不為空,則將原頭結點的前置節點指向該新建的節點
70             f.prev = newNode; 71         size++; // 元素數量+1
72         modCount++; // 修改次數+1
73 } 74     // 新增元素到尾結點(實現雙端佇列Deque中的方法)
75     public void addLast(E e) { 76 linkLast(e); 77 } 78     void linkLast(E e) { 79         final Node l = last; // 原尾結點
80         final Node newNode = new Node(l, e, null); // 新建一個節點,要新增的元素
81         last = newNode; // 將尾結點指向該新建的節點
82         if (l == null)  // 如果尾頭結點為空,說明原連結串列為空。這是新增的第一個元素,將頭結點也指向該新建的節點
83             first = newNode; 84         else    // 如果原尾結點不為空,則將原尾結點的後繼節點指向該新建的節點
85             l.next = newNode; 86         size++; // 元素數量+1
87         modCount++; // 修改次數+1
88 } 89 
90     // 佇列的新增方法
91     public boolean offer(E e) { 92         return add(e); 93 } 94 
95     // 棧的新增方法
96     public void push(E e) { 97 addFirst(e); 98     }
  • 刪除元素

poll():佇列的刪,獲取對頭元素並且對頭元素刪除。

pop():棧的刪,返回的是棧頂元素並將棧頂元素刪除。

1     public boolean remove(Object o) { 2         if (o == null) {
 3             for (Node x = first; x != null; x = x.next) { 4                 if (x.item == null) {
 5                     unlink(x);
 6                     return true;
 7                 }
 8             }
 9         } else { 10             for (Node x = first; x != null; x = x.next) { 11                 if (o.equals(x.item)) { 12 unlink(x); 13                     return true; 14 } 15 } 16 } 17         return false; 18 } 19     public E remove(int index) { 20 checkElementIndex(index); 21         return unlink(node(index)); 22 } 23     // 將該節點前置節點的下一個節點指向該節點後繼節點,將該節點後繼節點的上一個節點指向該節點前置節點。並將該節點置為空
24     E unlink(Node x) { 25         // assert x != null;
26         final E element = x.item; 27         final Node next = x.next; 28         final Node prev = x.prev; 29 
30         if (prev == null) { 31             first = next; 32         } else { 33             prev.next = next; 34             x.prev = null; 35 } 36 
37         if (next == null) { 38             last = prev; 39         } else { 40             next.prev = prev; 41             x.next = null; 42 } 43 
44         x.item = null; 45         size--; 46         modCount++; 47         return element; 48 } 49     // 將頭結點的下一個節點設定為新的頭結點,並將原頭節點置為空
50     private E unlinkFirst(Node f) { 51         // assert f == first && f != null;
52         final E element = f.item; 53         final Node next = f.next; 54         f.item = null; 55         f.next = null; // help GC
56         first = next; 57         if (next == null) 58             last = null; 59         else
60             next.prev = null; 61         size--; 62         modCount++; 63         return element; 64 } 65 
66     // 將尾結點的上一個節點設定為新的尾結點,並將原尾節點置為空
67     private E unlinkLast(Node l) { 68         // assert l == last && l != null;
69         final E element = l.item; 70         final Node prev = l.prev; 71         l.item = null; 72         l.prev = null; // help GC
73         last = prev; 74         if (prev == null) 75             first = null; 76         else
77             prev.next = null; 78         size--; 79         modCount++; 80         return element; 81 } 82 
83     public E remove() { 84         return removeFirst(); 85 } 86 
87     // 佇列的刪除方法
88     public E poll() { 89         final Node f = first; 90         return (f == null) ? null : unlinkFirst(f); 91 } 92     // 棧的刪除方法
93     public E pop() { 94         return removeFirst(); 95     }

附錄

LinkedList原始碼詳細註釋Github地址:

jdk1.8原始碼Github地址:

作者: Yanci丶
出處:https://www.cnblogs.com/Y2EX/p/14804514.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4328/viewspace-2807172/,如需轉載,請註明出處,否則將追究法律責任。

相關文章