LinkedList原始碼解析(jdk1.8)
本篇介紹的LinkedList是List介面的另一種實現,它的底層是基於雙向連結串列實現的,它具有插入刪除快而查詢修改慢的特點,此外,通過對雙向連結串列的操作還可以實現佇列和棧的功能。
一、類的繼承
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
二、類的屬性
//集合size
transient int size = 0;
//頭結點
transient Node<E> first;
//尾結點
transient Node<E> last;
LinkedList的底層結構如下圖所示:
F表示頭結點引用,L表示尾結點引用,連結串列的每個結點都有三個元素,分別是前繼結點引用(P),結點元素的值(E),後繼結點的引用(N)。結點由內部類Node表示,我們看看它的內部結構。
//集合結點內部類
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;
}
}
Node這個內部類只有三個成員變數和一個構造器,item表示結點的值,next為下一個結點的引用,prev為上一個結點的引用,通過構造器傳入這三個值。
三、建構函式
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this(); //先呼叫無參構造期
addAll(c);//呼叫add函式
}
有兩個構造器,一個是無參構造器,一個是傳入外部集合的構造器。與ArrayList不同的是LinkedList沒有指定初始大小的構造器。
四、add函式
1、在瞭解add方法之前,我們先了解LinkedList中linkLast(E)/linkBefore(E, Node<E>) ,這兩個方法是增加節點的方法:
a、linkLast方法總結就是:獲取最後一個節點,如果為空則新節點就是第一個節點,否則新節點就是當前集合最後一個節點的下一個節點。程式碼如下:
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++;
}
b、linkBefore方法:獲取引數節點的前一個節點,如果為空則生成的新節點為第一個節點,否則生成的新指定節點就是傳入引數節點的前一個節點
void linkBefore(E e, Node<E> succ) {
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++;
}
2、下面是關於add函式的幾個方法:
//方法1:在當前集合最後面加一個節點
public boolean add(E e) {
linkLast(e);
return true;
}
//方法2:根據下標在指定位置插入函式
public void add(int index, E element) {
checkPositionIndex(index); //檢查下標是否越界
if (index == size)
linkLast(element);
else
linkBefore(element, node(index)); //node(index):返回指定位置的node,詳細見下
}
//方法3:當前集合新增集合結果
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c); //將集合改成陣列,然後迴圈利用node的屬性新增集合元素(節點)
}
add(int index, E element)呼叫了node(index),程式碼如下:
Node<E> node(int 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;
}
}
總結:1、node(index)其實就是根據下標,然後迴圈依次獲取下個節點(或上個節點,二分法),獲得最紅節點;
2、add函式其實就是插入的時候,如果有指定的下標位置,那麼將後面的節點往後移一位,如果沒有指定下標位置就直接在連結串列最後新增一個節點。
五、remove函式
//根據下標刪除
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//刪除指定元素
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
remove函式找到刪除元素之後,呼叫了方法 unlink(Node) 進行節點刪除,它的程式碼如下:
E unlink(Node<E> x) {
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節點為給定節點的前一個節點(倒數第二個)
last = prev;
} else { //將下一個結點的前繼結點引用指向給定結點的前繼結點
next.prev = prev;
x.next = null; //將給定結點的下一個結點置空
}
x.item = null; //將節點元素設定為空
size--;
modCount++;
return element;
}
總結:remove函式第一步找到要刪除的元素,第二步則處理集合元素的要刪除節點的上個節點和下個節點;
六、get/set函式
//查詢,根據下標獲取,呼叫node(index)
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
//修改, 先查詢到節點,然後更換節點的元素
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
總結:對連結串列的查詢和修改操作都需要遍歷連結串列進行元素的定位,所以效率會低一點;
七、實現佇列和棧的操作
佇列:是一種特殊的線性表,特殊之處在於它只允許在表的前端進行刪除操作,而在表的後端進行插入操作。(先進先出)
棧:又名堆疊,它是一種運算受限的線性表,其限制是僅允許在表的一端進行插入和刪除運算。(先進後出)
LinkedList資料結構是基於連結串列的,所以通過對雙向連結串列的操作還可以實現單項佇列,雙向佇列和棧的功能。
不管是單向佇列還是雙向佇列還是棧,其實都是對連結串列的頭結點和尾結點進行操作,它們的實現都是基於addFirst(),addLast(),removeFirst(),removeLast()這四個方法,它們的操作和linkBefore()和unlink()類似,只不過一個是對連結串列兩端操作,一個是對連結串列中間操作。可以說這四個方法都是linkBefore()和unlink()方法的特殊情況。
八、總結
1、LinkedList是基於雙向連結串列實現的,不論是增刪改查方法還是佇列和棧的實現,都可通過操作結點實現
2、LinkedList無需提前指定容量,因為基於連結串列操作,集合的容量隨著元素的加入自動增加
3、LinkedList刪除元素後集合佔用的記憶體自動縮小,無需像ArrayList一樣呼叫trimToSize()方法
4、LinkedList的所有方法沒有進行同步,因此它也不是執行緒安全的,應該避免在多執行緒環境下使用
參考部落格:點選開啟連結
相關文章
- LinkedList原始碼分析(jdk1.8)原始碼JDK
- LinkedList原始碼解析原始碼
- ArrayList & LinkedList原始碼解析原始碼
- JAVA集合:LinkedList原始碼解析Java原始碼
- LinkedList 新增元素原始碼解析原始碼
- Java集合之LinkedList原始碼解析Java原始碼
- LinkedList 基本示例及原始碼解析原始碼
- Java HashMap 原始碼逐行解析(JDK1.8)JavaHashMap原始碼JDK
- JDK1.8原始碼解析(常見類)JDK原始碼
- Java集合-ArrayList原始碼解析-JDK1.8Java原始碼JDK
- java8LinkedList原始碼閱讀解析Java原始碼
- 六張圖詳解LinkedList 原始碼解析原始碼
- LinkedList原始碼原始碼
- Java併發包原始碼學習系列:JDK1.8的ConcurrentHashMap原始碼解析Java原始碼JDKHashMap
- LinkedList原始碼分析(一)原始碼
- 原始碼分析之 LinkedList原始碼
- LinkedList原始碼分析(二)原始碼
- LinkedList原始碼(add方法)原始碼
- 搞懂 Java LinkedList 原始碼Java原始碼
- 深入剖析LinkedList原始碼原始碼
- Java LinkedList 原始碼剖析Java原始碼
- LinkedList詳解-原始碼分析原始碼
- ArrayList、LinkedList和Vector的原始碼解析,帶你走近List的世界原始碼
- 原始碼分析–ArrayList(JDK1.8)原始碼JDK
- 原始碼分析–HashSet(JDK1.8)原始碼JDK
- ArrayList原始碼分析 jdk1.8原始碼JDK
- JDK1.8 hashMap原始碼分析JDKHashMap原始碼
- HashMap原始碼分析 JDK1.8HashMap原始碼JDK
- ArrayList原始碼分析(JDK1.8)原始碼JDK
- java基礎:LinkedList — 原始碼分析Java原始碼
- Java容器系列-LinkedList 原始碼分析Java原始碼
- LinkedList原始碼閱讀筆記原始碼筆記
- JDK1.8 ConcurrentHashMap原始碼閱讀JDKHashMap原始碼
- JDK1.8 原始碼分析(九)--LinkedHashMapJDK原始碼HashMap
- JDK1.8 原始碼分析(十) -- TreeMapJDK原始碼
- JDK1.8原始碼分析之HashMapJDK原始碼HashMap
- Java 集合系列之 LinkedList原始碼分析Java原始碼
- Java集合原始碼學習(3)LinkedListJava原始碼