轉(zhuǎn)自: https://zhuanlan.zhihu.com/p/47077000
已獲得作者同意
0x00 描述
LinkedList 是一個雙向鏈表,這是一個基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)。打開 LinkedList 源碼,可以看到它繼承于 AbstractSequentialList ,這個是 AbstractList 的子類。同時也實現(xiàn)了 List、Deque 、Clone、Serializable 接口。所以簡化的類關(guān)系圖可以表示為

關(guān)鍵屬性
-
size記錄當前數(shù)組元素的個數(shù) -
first鏈表頭指針 -
last鏈表尾部指針 -
modCount記錄修改次數(shù),這個字段是繼承于AbstractList
LinkedList 是實現(xiàn)了序列化接口 Serializable ,而以上屬性都被聲明為 transient 表示這些字段不參與序列化。
節(jié)點
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;
}
這個節(jié)點類,記錄鏈表中的節(jié)點的數(shù)據(jù),有前指針、后指針和具體的數(shù)據(jù)元素。這個數(shù)據(jù)這里用泛型來表示了。
構(gòu)造方法
public LinkedList() {
}
這個是默認構(gòu)造函數(shù),創(chuàng)建一個空鏈表。
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
這是通過列表來創(chuàng)建鏈表的。它調(diào)用了 addAll 方法。這個方法后文會講到。
0x01 常用方法
addFirst(E e)
在鏈表頭部添加節(jié)點
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
它實際是調(diào)用了內(nèi)部的一個私有方法 linkFirst 。只需要改變指針指向,時間復(fù)雜度O(1)。
addLast(E e)
public void addLast(E e) {
linkLast(e);
}
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++;
}
在鏈表尾部添加一個節(jié)點。它也是內(nèi)部的 linkLast 方法。這方法執(zhí)行效率也很高,只需要改變指針指向,時間復(fù)雜度是O(1)。
add(E e)
public boolean add(E e) {
linkLast(e);
return true;
}
可以看出也是調(diào)用了 linkLast 方法。
add(int index, E element)
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
在某個 index 前插入元素。
首先它會檢查 index 是否正確。如果在 0~size 范圍內(nèi)的下標,那么就執(zhí)行插入的方法;
它會判斷如果 index 是等于 size 那么就在尾部插入元素,否則就在 index 所在節(jié)點前面插入元素。
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++;
}
這個方法在節(jié)點 succ 前面添加元素,時間復(fù)雜度為O(1)。
在調(diào)用這個方法之前需要獲取到節(jié)點
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {//size >> 1 相當于 size/2
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;
}
}
在鏈表中要通過下標查找一個節(jié)點,需要通過遍歷。這里做了一個優(yōu)化,當 index 是在前半部分時從鏈表頭部開始遍歷;如果 index 超過當前鏈表的一半時則從后面開始遍歷查詢,它的時間復(fù)雜度為O(n)。
addAll(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
在尾部插入一個列表,通過調(diào)用 add(int,Collection) 來實現(xiàn)。
addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
//先檢測 index 是否有效
checkPositionIndex(index);
//以數(shù)組的形式獲取到列表數(shù)據(jù)
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
//找到index的前向指針,后向指針
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
//依次把數(shù)組中的節(jié)點插入到列表中
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和modCount
size += numNew;
modCount++;
return true;
}
這個方法稍微復(fù)雜一點
- 先檢測
index是否有效 - 以數(shù)組的形式獲取到列表數(shù)據(jù)
- 找到
index所在節(jié)點的前向指針,后向指針 - 依次把數(shù)組中的節(jié)點插入到列表中
- 鏈接后向指針的數(shù)據(jù)
- 更新
size和modCount
get(int index)
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
獲取 index 所在元素,通過 node 方法獲取。前面分析可以知道,這個方法需要遍歷,它的時間復(fù)雜度是O(n)。
contains(Object o)
public boolean contains(Object o) {
return indexOf(o) != -1;
}
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
查詢某個對象是否存在于該鏈表中是通過遍歷來實現(xiàn)的。
peek()
查看鏈表頭節(jié)點
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
peekFirst()
查看鏈表頭節(jié)點
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
peekLast()
查看鏈表尾部節(jié)點
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
poll()
獲取頭節(jié)點,并把頭節(jié)點從鏈表中刪除
public E poll() {
final Node<E> f = first;
return (f == null) ? null : 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 = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
pollFirst()
同上
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
pollLast()
獲取尾部節(jié)點,并將尾部節(jié)點刪除
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : 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 = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
remove()
刪除頭節(jié)點
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
clear()
清空鏈表
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
遍歷整個鏈表,將節(jié)點中的數(shù)據(jù)置為 null 。
0x02 總結(jié)
-
LinkedList是一個雙向鏈表,它是線程不安全的。 -
LinkedList擅長插入、刪除操作,時間復(fù)雜度是O(1);但是如果事先不知道被插入的節(jié)點,則需要通過遍歷來查詢到該節(jié)點,而查詢操作就不是很高效了,時間復(fù)雜度是O(n)。 -
get方法需要遍歷獲得,containts方法也需要遍歷 - 在鏈表頭部或尾部插入節(jié)點效率要高,但是通過下標
index插入節(jié)點則需要遍歷找到插入的位置,再執(zhí)行插入操作。
關(guān)注公眾號: “Java不睡覺”, 回復(fù):“資源”。獲取大數(shù)據(jù)全套視頻和大量Java書籍