addFirst 内部其實調用的是 linkFirst:
public void addFirst(E e) {
linkFirst(e);
}
linkFirst 負責把新的節點設為 first,并将新的 first 的 next 更新為之前的 first。
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++;
}
addLast 的核心其實和 addFirst 差不多,就交給大家自行了解了。
2)招式二:删
我這個删的招式還挺多的:
remove():删除第一個節點
remove(int):删除指定位置的節點
remove(Object):删除指定元素的節點
removeFirst():删除第一個節點
removeLast():删除最後一個節點
remove 内部調用的是 removeFirst,是以這兩個招式的功效一樣。
remove(int) 内部其實調用的是 unlink 方法。
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
unlink 方法其實很好了解,就是更新目前節點的 next 和 prev,然後把目前節點上的元素設為 null。
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;
}
remove(Object) 内部也調用了 unlink 方法,隻不過在此之前要先找到元素所在的節點:
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;
}
這内部就分為兩種,一種是元素為 null 的時候,必須使用 == 來判斷;一種是元素為非 null 的時候,要使用 equals 來判斷。equals 是不能用來判 null 的,會抛出 NPE 錯誤。
removeFirst 内部調用的是 unlinkFirst 方法:
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
unlinkFirst 負責的就是把第一個節點毀屍滅迹,并且捎帶把後一個節點的 prev 設為 null。
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;
}
3)招式三:改
可以調用 set() 方法來更新元素:
list.set(0, "沉默王五");
1
來看一下 set() 方法:
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
首先對指定的下标進行檢查,看是否越界;然後根據下标查找原有的節點:
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;
}
}
size >> 1:也就是右移一位,相當于除以 2。對于計算機來說,移位比除法運算效率更高,因為資料在計算機内部都是二進制存儲的。
換句話說,node 方法會對下标進行一個初步判斷,如果靠近前半截,就從下标 0 開始周遊;如果靠近後半截,就從末尾開始周遊。
找到指定下标的節點就簡單了,直接把原有節點的元素替換成新的節點就 OK 了,prev 和 next 都不用改動。
4)招式四:查
我這個查的招式可以分為兩種:
indexOf(Object):查找某個元素所在的位置
get(int):查找某個位置上的元素
indexOf 的内部分為兩種,一種是元素為 null 的時候,必須使用 == 來判斷;一種是元素為非 null 的時候,要使用 equals 來判斷。因為 equals 是不能用來判 null 的,會抛出 NPE 錯誤。
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;
}
get 方法的核心其實還是 node 方法,這個之前已經說明過了,這裡略過。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
其實,查這個招式還可以演化為其他的一些,比如說:
getFirst() 方法用于擷取第一個元素;
getLast() 方法用于擷取最後一個元素;
poll() 和 pollFirst() 方法用于删除并傳回第一個元素(兩個方法盡管名字不同,但方法體是完全相同的);
pollLast() 方法用于删除并傳回最後一個元素;
peekFirst() 方法用于傳回但不删除第一個元素。
四、LinkedList 的挑戰
說句實在話,我不是很喜歡和師兄 ArrayList 拿來比較,因為我們各自修煉的内功不同,沒有孰高孰低。
雖然師兄經常喊我一聲師弟,但我們之間其實挺和諧的。但我知道,在外人眼裡,同門師兄弟,總要一較高下的。
比如說,我們倆在增删改查時候的時間複雜度。
也許這就是命運吧,從我進入師門的那天起,這種争論就一直沒有停息過。
無論外人怎麼看待我們,在我眼裡,師兄永遠都是一哥,我敬重他,他也願意保護我。
好了,LinkedList 這篇就到這了。
如果大家有閑情逸緻的話,建議手撕一下連結清單,可以從單向連結清單開始撕起。
希望大家能點贊下,給我注入一點點更新的動力。我也會不斷地提升品質,給大家帶來更硬核的技術文章,筆芯~