Linkedlist就是這麼簡單

一. 概述
LinkedList 是 Java 集合中比較常用的數據結構,與 ArrayList 一樣,實現了 List 接口,只不過 ArrayList 是基於數組實現的,而 LinkedList 是基於鏈表實現的。所以 LinkedList 插入和刪除方面要優於 ArrayList,而隨機訪問上則 ArrayList 性能更好。

除了 LIst 接口之外,LinkedList 還實現了 Deque,Cloneable,Serializable 三個接口。這說明該數據結構支持隊列,克隆和序列化操作的。與 ArrayList 一樣,允許 null 元素的存在,且是不支持多線程的。

二. 源碼解讀
屬性

LinkedList 提供了以下三個成員變量。size,first,last。

transient int size = 0;
transient Node<E> first;
transient Node<E> last;
其中 size 爲 LinkedList 的大小,first 和 last 分別爲鏈表的頭結點和尾節點。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 是 LInkedList 的內部類,定義了存儲的數據元素,前一個節點和後一個節點,典型的雙鏈表結構。

構造方法

public LinkedList() {}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList 提供了兩個構造方法:LinkedList() 和 LinkedList(Collection<? extends E> c)。

LinkedList() 僅僅構造一個空的列表,沒有任何元素。size = 0。first 和 last 都爲 null。

後一個構造方法構造一個包含指定 Collection 中所有元素的列表,該構造方法首先會調用空的構造方法,然後通過 addAll() 的方式把 Collection 中的所有元素添加進去。

調用 addAll() 方法,傳入當前的節點個數 size,此時 size 爲
檢查 index 是否越界
將 collection 轉換成數組
遍歷數組,將數組裏面的元素創建爲節點,並按照順序連起來。
修改當前的節點個數 size 的值
操作次數 modCount 自增 1.
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;
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
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 += numNew;
modCount++;
return true;
}
add 操作

添加元素到鏈表末尾

public boolean add(E e) {
linkLast(e);
return true;
}
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++;
}
add 方法直接調用了 linkLast 方法,而 linkLast 方法是不對外開放的。該方法做了三件事情,新增一個節點,改變其前後引用,將 size 和 modCount 自增 1。其中 modCount 是記錄對集合操作的次數。

在指定的位置插入元素

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;
}
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++;
}
首先檢查下標是否越界,然後判斷如果 index == size 則添加到末尾,否則將該元素插入的 index 的位置。其中 node(index) 是獲取 index 位置的節點,linkBefore 負責把元素 e 插入到 succ 之前。

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() 方法這裏寫的還是挺讚的,不是傻乎乎的從頭到尾或者從尾到頭遍歷鏈表,而是將 index 與 當前鏈表的一半做對比,比一半小從頭遍歷,比一半大從後遍歷。對於數據量很大時能省下不少時間。

get 操作

很簡單,首先獲取節點,然後返回節點的數據即可。

public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
remove 操作

移除指定位置的元素

public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
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;
}
先檢查下標是否越界,然後調用 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,然後在遍歷鏈表,找到鈣元素第一次出現的位置,移除並返回 true。

像其他的常用方法如:getFirst, getLast, removeFirst, removeLast, addFirst, addLast 等都很簡單,掃一眼源碼就能懂,我這裏就不寫了。

迭代器

LInkedList 的 iterator() 方法是在其父類 AbstractSequentialList 中定義的,最終一路 debug 到 LinkedList 類這裏。其中 index 爲 零。

public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
我們來看看 ListItr。

private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
篇幅有限 ,我就只貼主要代碼了。由源碼可以看出初始化 ListItr 時,將 nextIndex 指向 index, 也就是零。如果該集合爲空,那麼 index == size 爲 true,next 指向 null,否則 next 指向下標爲零的元素,也就是第一個。

hasNext 直接返回 nextIndex < size 簡單明瞭。下面看看 next 方法,首先檢查 expectedModCount 與 modCount 是否相等,看似無關緊要的代碼保證了集合在迭代過程中不被修改[包括新增刪除節點等]。然後將 lastReturned 指向 next,next 後移一個節點,nextIndex 自增 1,並返回 lastReturned 節點的元素。

總結
1、從源碼可以看出 LinkedList 是基於鏈表實現的。如下圖:
Linkedlist就是這麼簡單

2、在查找和刪除某元素時,區分該元素爲 null和不爲 null 兩種情況來處理,LinkedList 中允許元素爲 null。

3、基於鏈表實現不存在擴容問題。

4、查找時先判斷該節點位於前半部分還是後半部分,加快了速度

5、因爲基於鏈表,所以插入刪除極快,查找比較慢。

6、實現了棧和隊列的相關方法,所以可作爲棧,隊列,雙端隊列來用
瞭解更多

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章