jdk原始碼分析之LinkedList
LinkedList關鍵屬性
size表示當前連結串列儲存了多少資料,first指標指向連結串列第一個資料,last指標指向連結串列最後一個資料
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
LinkedList底層資料結構
Linked是一個雙向連結串列,連結串列節點的資料如下,有一個資料域和兩個指標域
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList首尾新增資料linkFirst(E e)和linkLast(E e)
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
先儲存舊連結串列的尾指標為l,然後根據傳入的資料e和為指標l構建一個資料節點。
newNode = new Node<>(l, e, null)
將連結串列的尾節點修改為新建立的節點newNode。如果l==null,表示之前連結串列裡邊沒有儲存資料,現在新增了一個新資料,頭指標和尾指標都應該指向這個新新增的節點
last = newNode;
if (l == null)
first = newNode;
如果之前連結串列裡邊儲存的有資料,l!=null,則修改新節點連結到原連結串列的尾部
l.next = newNode;
然後修改size和modCount(迭代器併發訪問用到的屬性)
linkFirst(E e)程式碼類似,原理一樣
在LinkedList的某一節點前插入資料linkBefore
/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
在節點succ前插入資料 e,則succ是e的後繼節點,e的前序就是succ的前序,因此根據e、前序、後繼構造一個新的節點newNode = new Node<>(pred, e, succ),修改succ的前序節點為新節點newNode,succ.prev = newNode,然後再將斷開的連結串列連結起來
LinkedList刪除頭結點unlinkFirst和尾節點unlinkLast
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
首先儲存頭結點的後繼節點指標(即將成為新的頭結點),然後頭結點指標域和資料域置null,加快記憶體回收速度,然後判斷next是否為null,null的話表示原連結串列只有一個節點,現在還把唯一的節點刪除了,因此first頭指標和last尾指標都應該為null。next不為null的話表示刪除頭結點後連結串列還有資料,修改next的prev指標為null即可(pre指標為null表示此接節點為隊首資料,next指標為null表示隊尾資料)
unlinkLast(Node l) 程式碼和原理類似
LinkedList刪除某一節點unlink
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
仍然是指標操作,提取待刪除節點的資料域、前序和後繼,資料域用於返回值,前序和後繼用於連結串列的斷連操作
前序為null表示帶刪除節點是隊首節點,修改first指標為待刪除節點的後續節點
前序節點不為null就把前序節點的next指標指向後繼節點,待刪除節點的prev指標域置null,這樣待刪除節點的前序節點指標已經斷開重連了,然後處理後繼節點指標的斷開重連
判斷後繼節點是否為null,是null的話表示待刪除節點就是隊尾,修改隊尾指標last為待刪除節點的前序
後繼節點不為null,後繼節點的prev指標指向待刪除節點的前序節點,待刪除節點的next指標置null,這樣待刪除節點的後繼結點的指標也已經斷開重連了
然後把待刪除節點的資料域置null,修改size和modCount,返回刪除節點的資料
LinkedList的隨機訪問node(int index)
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
連結串列插入刪除新增資料比較方便,直接修改指標的斷連即可。但是連結串列並不能夠像陣列那樣隨機訪問,只能進行遍歷。LinkedList是一個雙向連結串列,可以對遍歷做一個小優化,只遍歷半個連結串列即可。
如果位置在連結串列前半部分index < (size >> 1),正向遍歷查詢for (int i = 0; i < index; i++)
否則逆向遍歷查詢for (int i = size - 1; i > index; i–)
LinkedList的某一位置新增一個集合addAll
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
首先檢查插入位置是否合法
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
可見插入位置區間為【0,size】,可以隊首插入,可以隊尾插入,中間任意位置當然也可以
然後把集合物件轉化為陣列,陣列長度為0表示集合沒有資料,直接返回false表示插入失敗
構建插入節點的前序和後繼,如果插入位置index==size的話表示隊尾插入資料,因此後續為null,前序為隊尾last
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
}
不是在隊尾插入集合的話需要根據插入位置index找到後繼節點,找到了後繼也就找到了前序
else {
succ = node(index);
pred = succ.prev;
}
Node node(int index)方法用於返回特定位置的節點,這樣插入點的前序和後繼都找到了,開始迴圈把集合的資料新增到連結串列的特定位置
for (Object o : a) {
E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
通過new Node<>(pred, e, null)建立新節點,前序指標在建立的時候在建構函式裡邊已經指明。
pred == null表示是在隊首新增資料,需要修改隊首指標first
否則把前序節點的後繼指標設定為newNode
再把前序節點設為newNode,接著下一次迴圈
這樣在迴圈結束後,只有最後一個新增的節點的後繼指標沒有處理
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
如果succ後繼為null,修改隊尾指標為last為最後一個新增的節點pred
否則設定最有一個新增的節點的後繼指標為succ,succ的前序指標指向最後一個新增的節點pred
最後修改size和modCount,返回true表示新增成功
LinkedList的雙端佇列特性
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList實現了Deque介面,因此可以吧LinkedList當做一個雙端佇列屬性,比如peek和poll,均是通過連結串列的基本操作實現相應的功能,其他的方法類似,不再一一列舉。
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
相關文章
- LinkedList原始碼分析(jdk1.8)原始碼JDK
- 【集合框架】JDK1.8原始碼分析之LinkedList(七)框架JDK原始碼
- 原始碼分析之 LinkedList原始碼
- jdk原始碼分析之TreeMapJDK原始碼
- jdk原始碼分析之PriorityQueueJDK原始碼
- jdk原始碼分析之WeakHashMapJDK原始碼HashMap
- jdk原始碼分析之ArrayListJDK原始碼
- jdk原始碼分析之HashMapJDK原始碼HashMap
- jdk原始碼分析之CopyOnWriteArrayListJDK原始碼
- LinkedList原始碼解析(jdk1.8)原始碼JDK
- Java 集合系列之 LinkedList原始碼分析Java原始碼
- LinkedList原始碼分析原始碼
- 原始碼|jdk原始碼之HashMap分析(一)原始碼JDKHashMap
- 原始碼|jdk原始碼之HashMap分析(二)原始碼JDKHashMap
- jdk原始碼分析之ConcurrentHashMapJDK原始碼HashMap
- jdk原始碼分析之LinkedHashMapJDK原始碼HashMap
- 死磕 java集合之LinkedList原始碼分析Java原始碼
- 死磕 jdk原始碼之HashMap原始碼分析JDK原始碼HashMap
- LinkedList原始碼分析(一)原始碼
- LinkedList原始碼分析(二)原始碼
- Java LinkedList 原始碼分析Java原始碼
- JDK1.8原始碼分析之HashMapJDK原始碼HashMap
- jdk1.8原始碼之HashMap分析JDK原始碼HashMap
- LinkedList詳解-原始碼分析原始碼
- LinkedList與Queue原始碼分析原始碼
- 原始碼|jdk原始碼之棧、佇列及ArrayDeque分析原始碼JDK佇列
- 面試必備:LinkedList原始碼解析(JDK8)面試原始碼JDK
- 走進 JDK 之 LinkedListJDK
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(1)JDK原始碼
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(2)JDK原始碼
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(3)JDK原始碼
- 【JDK】JDK原始碼分析-ReentrantLockJDK原始碼ReentrantLock
- Java容器系列-LinkedList 原始碼分析Java原始碼
- java基礎:LinkedList — 原始碼分析Java原始碼
- 從面試角度分析LinkedList原始碼面試原始碼
- 【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析JDK原始碼BloC
- JDK原始碼分析-TreeSetJDK原始碼
- 【JUC】JDK1.8原始碼分析之CyclicBarrier(四)JDK原始碼