前言: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日,上午。