概述
LinkedList是一種可以在任何位置進行高效地插入和移除操作的有序序列,它是基於雙向鏈表實現的,是線程不安全的,允許元素爲null的雙向鏈表。
源碼分析
1. 變量
/**
* 集合元素數量
**/
transient int size = 0;
/**
* 指向第一個節點的指針
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 指向最後一個節點的指針
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
2. 構造方法
/**
* 無參構造方法
*/
public LinkedList() {
}
/**
* 將集合c所有元素插入鏈表中
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
3. 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既有prev也有next,所以證明它是一個雙向鏈表。
4. 添加元素
addAll(Collection<? extends E> c)
將集合c添加到鏈表,如果不傳index,則默認是添加到尾部。如果調用
addAll(int index, Collection<? extends E> c)
方法,則添加到index後面。
/**
* 將集合添加到鏈尾
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
/**
*
*/
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
// 拿到目標集合數組
Object[] a = c.toArray();
//新增元素的數量
int numNew = a.length;
//如果新增元素數量爲0,則不增加,並返回false
if (numNew == 0)
return false;
//定義index節點的前置節點,後置節點
Node<E> pred, succ;
// 判斷是否是鏈表尾部,如果是:在鏈表尾部追加數據
//尾部的後置節點一定是null,前置節點是隊尾
if (index == size) {
succ = null;
pred = last;
} else {
// 如果不在鏈表末端(而在中間部位)
// 取出index節點,並作爲後繼節點
succ = node(index);
// index節點的前節點 作爲前驅節點
pred = succ.prev;
}
// 鏈表批量增加,是靠for循環遍歷原數組,依次執行插入節點操作
for (Object o : a) {
@SuppressWarnings("unchecked")
// 類型轉換
E e = (E) o;
// 前置節點爲pred,後置節點爲null,當前節點值爲e的節點newNode
Node<E> newNode = new Node<>(pred, e, null);
// 如果前置節點爲空, 則newNode爲頭節點,否則爲pred的next節點
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
// 循環結束後,如果後置節點是null,說明此時是在隊尾追加的
if (succ == null) {
// 設置尾節點
last = pred;
} else {
//否則是在隊中插入的節點 ,更新前置節點 後置節點
pred.next = succ;
succ.prev = pred;
}
// 修改數量size
size += numNew;
//修改modCount
modCount++;
return true;
}
/**
* 取出index節點
*/
Node<E> node(int index) {
// assert isElementIndex(index);
// 如果index 小於 size/2,則從頭部開始找
if (index < (size >> 1)) {
// 把頭節點賦值給x
Node<E> x = first;
for (int i = 0; i < index; i++)
// x=x的下一個節點
x = x.next;
return x;
} else {
// 如果index 大與等於 size/2,則從後面開始找
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
// 檢測index位置是否合法
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 檢測index位置是否合法
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
假設我們要在index=2處添加{1,2}到鏈表中,圖解如下:
第一步:拿到index=2的前驅節點 prev=ele1
第二步:遍歷集合prev.next=newNode,並實時更新prev節點以便下一次
遍歷:prev=newNode
第三步:將index=2的節點ele2接上:prev.next=ele2,ele2.prev=prev
注意node(index)
方法:尋找處於index的節點,有一個小優化,結點在前半段則從頭開始遍歷,在後半段則從尾開始遍歷,這樣就保證了只需要遍歷最多一半結點就可以找到指定索引的結點。
addFirst(E e)方法
將e元素添加到鏈表並設置其爲頭節點(first)。
public void addFirst(E e) {
linkFirst(e);
}
//將e鏈接成列表的第一個元素
private void linkFirst(E e) {
final Node<E> f = first;
// 前驅爲空,值爲e,後繼爲f
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
//若f爲空,則表明列表中還沒有元素,last也應該指向newNode
if (f == null)
last = newNode;
else
//否則,前first的前驅指向newNode
f.prev = newNode;
size++;
modCount++;
}
- 拿到first節點命名爲f
- 新創建一個節點newNode設置其next節點爲f節點
- 將newNode賦值給first
- 若f爲空,則表明列表中還沒有元素,last也應該指向newNode;否則,前first的前驅指向newNode。
- 圖解如下:
addLast(E e)方法
將e元素添加到鏈表並設置其爲尾節點(last)。
public void addLast(E e) {
linkLast(e);
}
/**
* 將e鏈接成列表的last元素
*/
void linkLast(E e) {
final Node<E> l = last;
// 前驅爲前last,值爲e,後繼爲null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//最後一個節點爲空,說明列表中無元素
if (l == null)
//first同樣指向此節點
first = newNode;
else
//否則,前last的後繼指向當前節點
l.next = newNode;
size++;
modCount++;
}
過程與linkFirst()
方法類似,這裏略過。
add(E e)方法
在尾部追加元素e。
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
// 前驅爲前last,值爲e,後繼爲null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//最後一個節點爲空,說明列表中無元素
if (l == null)
//first同樣指向此節點
first = newNode;
else
//否則,前last的後繼指向當前節點
l.next = newNode;
size++;
modCount++;
}
add(int index, E element)方法
在鏈表的index處添加元素element.
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* 在succ節點前增加元素e(succ不能爲空)
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 拿到succ的前驅
final Node<E> pred = succ.prev;
// 新new節點:前驅爲pred,值爲e,後繼爲succ
final Node<E> newNode = new Node<>(pred, e, succ);
// 將succ的前驅指向當前節點
succ.prev = newNode;
// pred爲空,說明此時succ爲首節點
if (pred == null)
// 指向當前節點
first = newNode;
else
// 否則,將succ之前的前驅的後繼指向當前節點
pred.next = newNode;
size++;
modCount++;
}
linkLast
方法上文有講。
linkBefore(E e, Node<E> succ)
方法步驟:
- 拿到succ的前驅節點
- 新new節點:前驅爲pred,值爲e,後繼爲succ :
Node<>(pred, e, succ);
- 將succ的前驅指向當前節點
- pred爲空,說明此時succ爲首節點,first指向當前節點;否則,將succ之前的前驅的後繼指向當前節點
5. 獲取/查詢元素
get(int index)方法
根據索引獲取鏈表中的元素。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
// 檢測index合法性
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 根據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;
}
}
node方法上文有詳細講解,這裏不做介紹。
getFirst()方法
獲取頭節點。
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
getLast()方法
獲取尾節點。
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
6. 刪除元素
remove(Object o)
根據Object對象刪除元素。
public boolean remove(Object o) {
// 如果o是空
if (o == null) {
// 遍歷鏈表查找 item==null 並執行unlink(x)方法刪除
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;
}
E unlink(Node<E> x) {
// assert x != null;
// 保存x的元素值
final E element = x.item;
//保存x的後繼
final Node<E> next = x.next;
//保存x的前驅
final Node<E> prev = x.prev;
//如果前驅爲null,說明x爲首節點,first指向x的後繼
if (prev == null) {
first = next;
} else {
//x的前驅的後繼指向x的後繼,即略過了x
prev.next = next;
// x.prev已無用處,置空引用
x.prev = null;
}
// 後繼爲null,說明x爲尾節點
if (next == null) {
// last指向x的前驅
last = prev;
} else {
// x的後繼的前驅指向x的前驅,即略過了x
next.prev = prev;
// x.next已無用處,置空引用
x.next = null;
}
// 引用置空
x.item = null;
size--;
modCount++;
// 返回所刪除的節點的元素值
return element;
}
- 遍歷鏈表查找 item==null 並執行unlink(x)方法刪除
- 如果前驅爲null,說明x爲首節點,first指向x的後繼,x的前驅的後繼指向x的後繼,即略過了x.
- 如果後繼爲null,說明x爲尾節點,last指向x的前驅;否則x的後繼的前驅指向x的前驅,即略過了x,置空x.next
- 引用置空:
x.item = null
-
圖解:
remove(int index)方法
根據鏈表的索引刪除元素。
public E remove(int index) {
checkElementIndex(index);
//node(index)會返回index對應的元素
return unlink(node(index));
}
E unlink(Node<E> x) 方法上文有詳解。
removeFirst()方法
刪除頭節點。
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
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指向前first的後繼,也就是列表中的2號位
first = next;
//如果此時2號位爲空,那麼列表中此時已無節點
if (next == null)
//last指向null
last = null;
else
// 首節點無前驅
next.prev = null;
size--;
modCount++;
return element;
}
原理與添加頭節點類似。
removeLast()方法
刪除尾節點(last)
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
// 取出尾節點中的元素
final E element = l.item;
// 取出尾節點中的後繼
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
// last指向前last的前驅,也就是列表中的倒數2號位
last = prev;
// 如果此時倒數2號位爲空,那麼列表中已無節點
if (prev == null)
// first指向null
first = null;
else
// 尾節點無後繼
prev.next = null;
size--;
modCount++;
// 返回尾節點保存的元素值
return element;
}
7. 修改元素
修改元素比較簡單,先找到index對應節點,然後對值進行修改。
public E set(int index, E element) {
checkElementIndex(index);
// 獲取到需要修改元素的節點
Node<E> x = node(index);
// 保存之前的值
E oldVal = x.item;
// 執行修改
x.item = element;
// 返回舊值
return oldVal;
}
8. 與ArrayList的對比
優點:
- 不需要擴容和預留空間,空間效率高
- 增刪效率高
缺點:
- 隨機訪問時間效率低
- 改查效率低
GitHub地址: https://github.com/fantj2016/java-reader