LinkedList
- 特點
- 允許null值
- 內部以雙向鏈表的形式來保存集合中的元素查詢慢,增刪快(相比於ArrayList少了數組拷貝)
- 線程不安全
- 所有指定位置的操作都是從頭開始遍歷進行的
- 素是有序的,輸出順序與輸入順序一致
2.理論與實操同樣重要,知其然也要知其所以然
LinkedList查詢慢,增刪快與ArrayList相反,但同時都是線程不安全的
首先分析其構造方法
//空參構造(使用最多)
public LinkedList() {
}
//有參構造
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);//添加所有集合元素
}
分析方法實現原理源碼
1. 添加方法其一
- 原理:
首先看單向鏈表添加如何實現:
點擊這裏可以自行操作觀察
每次增加一個元素,最後一個元素節點會指向新增加元素(2指向3),我習慣稱爲前一個元素的後繼集節點指向這個元素
同理,在java中,因爲是雙向鏈表所以,還有一個節點,我習慣稱爲元素的前驅節點指向前一個元素(3指向2)
下面探究java中如何實現這一操作
/
public boolean add(E e) {
linkLast(e);//實則調用這個方法,如下
return true;
}
/**
* 先分析: 1、雙向鏈表包括首節點first,尾節點last,元素節點
* 2、每一個元素節點包括三部分:元素、前驅節點、後繼節點
* 3、添加元素又分爲首次添加、非首次添加
* 4、首次添加:first爲空節點,last爲空節點,此時創建新元素節點
* 前繼節點則爲空,後繼節點也爲空,添加元素後,此元素節點既是first也爲last
* 5、非首次添加:創建新元素節點,添加到鏈表最後,此時應讓新元素節點的前驅節點指向當前尾節點(在創建新節點時調用的構造函數中完成)
* 然後新元素變爲新的尾節點,因爲是雙向鏈表,需要之前尾節點的後繼節點同樣指向新的尾節點節點完成雙向
*/
void linkLast(E e) {
final Node<E> l = last;//此時l代表當前尾節點
/**創建一個節點:l代表尾節點(元素前驅節點)、e當前節點、null代表尾節點(元素後繼節點)*/
final Node<E> newNode = new Node<>(l, e, null);//Node方法如下文
//此時讓尾節點指向新添加的元素,作爲尾節點的標記(上述新的尾節點)
last = newNode;
if (l == null)//在第一次添加元素時,last爲空所以l爲空,則這個元素就是第一個節點(首節點)
first = newNode;//引出首節點,作爲首節點的標記(同時也是尾節點)
else//若不是第一次添加元素,則尾節點last不爲空,l則也不爲空,
l.next = newNode;//(爲了滿足雙向鏈表)當前尾節點的後繼節點指向新添加的元素節點,至此新添加元素節點變爲尾節點(完成雙向鏈接)
size++;//鏈表元素個數加一
modCount++;//鏈表操作次數記錄加一
}
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;//在創建新節點時,前繼節點在這裏完成了指向當前尾節點
}
}
2. 添加方法其二
原理:時刻考慮雙向鏈表特性,指定位置添加元素時,改變新增元素前後繼節點,以及後元素節點的前指向,和前元素節點的後指向,下圖上方爲新增節點,在1處新增示意圖(大概一個示意圖,有點醜),與上述唯一區別就是此方法時在中間添加元素
源碼:
//index指定添加元素的位置,element元素值
public void add(int index, E element) {
//判斷索引是否越界或者非法(小於零或者大於鏈表長度)
checkPositionIndex(index);
//若index==size證明需要在鏈表末尾添加元素(和第一種添加方法實現一致)
if (index == size)
linkLast(element);//同上方法一
else//反之,在指定索引添加元素 首先node(index)找出該索引對應的元素節點
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {
// 獲取指定位置元素的前驅節點
final Node<E> pred = succ.prev;
//創建新節點,前驅節點指向原指定位置的前繼節點,後繼結點指向succ節點
//例:1<——2(newNode)——>3
final Node<E> newNode = new Node<>(pred, e, succ);
//上述例子 因爲是雙向鏈表所以要同時讓2<——3
succ.prev = newNode;
if (pred == null)
//pred 爲空證明新添加的元素是第一個節點,那麼更新首節點
first = newNode;
else
//反之,也要設置1——>2,保證雙向鏈表
pred.next = newNode;
//元素個數加一
size++;
//記錄操作次數
modCount++;
}
其他的添加方法原理基本一樣
3. 刪除方法
- 原理就是完成下圖的整個過程(有了想法纔有可能實現!)
指定對象刪除
public boolean remove(Object o) {
if (o == null) {//若刪除null元素
for (Node<E> x = first; x != null; x = x.next) {//從第一個元素開始,首節點給x,判斷條件是x是否有值,有的話進入循環體,然後將x的後繼節點付給x(後繼節點就相當於下一個元素)
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;
}
指定索引刪除
public E remove(int index) {
checkElementIndex(index);//判斷索引是否爲合法(小於零或者大於鏈表長度)
return unlink(node(index));//node(index)找到元素節點直接刪除如下
}
Node<E> node(int index) {
//根據索引查找元素這裏體現了雙向鏈表
if (index < (size >> 1)) {//index小於元素總個數的一半,從前找更快
//從前往後找
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;//順藤摸瓜找到index位置元素
return x;
} else {//反之從後找更快
//從後往前找
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
發現上述方法就是通過傳入的條件找到了要刪除的元素最終通過unlink()方法刪除下面分析這個方法
E unlink(Node<E> x) {
//x就是要刪除的節點,從節點中取出要刪除的元素
final E element = x.item;
//取出後繼節點
final Node<E> next = x.next;
//取出前驅節點
final Node<E> prev = x.prev;
//下方兩個if語句的執行代表了上圖過度過程的實現(解鏈操作)
if (prev == null) {//若前驅節點爲null證明,這是第一個元素
first = next;//將要刪除的元素節點的下一個元素節點作爲第一個元素節點(後繼節點指向的元素)
} else {//反之則爲中間元素節點
prev.next = next;//上圖中過度過程的上方藍線形成1的後繼節點指向3(通過2),同時1指向2紅線斷裂(被重新賦值覆蓋了)
x.prev = null;//斷開2的前驅節點(斷開了2指向1的那條紅線)
}
if (next == null) {//若是最後一個元素
last = prev;//將最後一個元素的前一個元素設置爲最後一個元素,記錄到尾節點中
} else {
next.prev = prev;//上圖中過度過程的下方藍線形成3的前驅節點指向1(通過2)同時3指向2紅線斷裂(被重新賦值覆蓋了)
x.next = null;//斷開2的後繼節點(斷開了2指向3的那條紅線)
}
//最後清空元素,到此元素節點被成功刪除
x.item = null;
size--;//元素個數自減
modCount++;//操作次數自增
return element;//返回這個被刪除的元素
}
掌握上述方法後再看其他相關刪除方法代碼,邏輯都一樣
4. 查找元素get()方法
public E get(int index) {
checkElementIndex(index);
return node(index).item;//在刪除方法中已經說過node方法,得到節點後取出元素返回
}
5. 遍歷
- 原理:從第一個元素或者最後一個元素開始遍歷,邏輯就是根據得到的第一個元素節點順藤摸瓜到最後一個元素節點,比較簡單不累贅了,(可參考上篇文章arrayList的遍歷原理一樣,只不過鏈表是以元素向下尋找,有興趣可自行跟進源碼查看),遍歷耗時很慢,每次查找都要從頭開始,一般不採用。
好了LinkedList討論到此結束,下篇文章討論List集合下的Vector,每天進步一點點!!大家一起討論學習,不足的地方還請指出來!