LinkedList 做爲 List
的另外一種實現,也很是的經典。與 ArrayList 不一樣,LinkedList 底層使用的是雙向鏈表來實現的,具體類圖以下:java
相比於 ArrayList,LinkedList 繼承了 AbstractSequentialList 類,並且實現了 Deque 接口,RandomAccess 接口就被沒有實現。微信
但在實際的使用當中,LinkedList 使用的並無 ArrayList 多,LinkedList 能夠被當作隊列和棧來使用,可是 BlockingQueue
使用的比它更爲普遍,由於通常使用隊列的地方都會涉及到比較高的併發,在高併發的狀況下,BlockingQueue
比 LinkedList 更好用,BlockingQueue
之後會寫專門的文章來介紹。數據結構
本文基於 JDK1.8併發
LinkedList 的結構比 ArrayList 更簡單,核心的成員變量以下,size
記錄當前元素的個數,first
指向頭結點,last
指向尾節點。dom
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
複製代碼
Node
的代碼以下,經過泛型來存儲具體的元素,每一個節點均可以獲取前一個或者後一個節點,因此 LinkedNode 底層數據結構是雙向鏈表。函數
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;
}
}
複製代碼
由於底層使用的雙向鏈表,因此理論上來講 LinkedList 的容量是沒有限制的,天然也沒有了擴容的過程。高併發
LinkedList 的實例化過程也相對簡單,只提供了兩個構造函數。源碼分析
一個不帶任何參數,也不須要作任何的數據初始化,頭結點和尾節點的初始化都放在添加第一個元素的時候。post
public LinkedList() {
}
複製代碼
第二個構造函數接收一個 Collection
類型的對象,會把對象中的全部元素都添加到當前 LinkedList 對象中。this
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
複製代碼
與 ArrayList 相比,LinkedList 有以下特色:
由於 LinkedList 沒有實現 RandomAccess
接口,再加上自己底層的數據結構是雙向鏈表,因此對鏈表中的元素不能隨機訪問,只能按照順序訪問。
並且對於鏈表來講,元素時能夠無限擴展(理論上)的,因此 LinkedList 的容量也沒有上限。
從 JDK1.6 開始,LinkedList 實現了 Deque
接口,這就代表 LinkedList 能夠用做隊列或者雙向隊列。
List 中有的方法,LinkedList 中都實現了。
能夠添加元素:
public boolean add(E e) {
linkLast(e);
return true;
}
複製代碼
能夠看到,添加元素的操做其實是用 linkLast()
方法來完成的,在添加元素的過程當中,若是發現頭結點爲空,那說明是添加第一個元素,只須要把頭結點指向剛添加的節點就能夠,如下代碼是在尾部添加一個節點:
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++;
}
複製代碼
上面是把元素添加到鏈表的尾部,由於 LinkedList 還能夠被用做隊列和棧,所以還提供了從頭部添加元素的方法:
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++;
}
複製代碼
既然有添加元素的操做,也有刪除元素的操做,代碼以下:
private E unlinkLast(Node<E> l) {
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null;
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
複製代碼
清空 LinkedList 就是把全部的元素置爲 null:
public void clear() {
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++;
}
複製代碼
從以上代碼能夠發現,LinkedList 的操做都是對鏈表的操做。
作爲普通隊列時,能夠在隊列中進行入隊和出隊的操做。從隊列頭部獲取一個元素,可是不刪除元素 peek()
:
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
複製代碼
從隊列頭部獲取一個元素並刪除,poll()
:
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
複製代碼
在隊列的尾部添加一個元素,offer()
:
public boolean offer(E e) {
return add(e);
}
複製代碼
普通隊列只能夠在一端入隊,在另外一端出隊,可是對於雙向隊列,能夠在兩端執行入隊和出隊操做。
因此在在做爲雙向隊列時,拿 peek 操做來講便可以 peekFirst()
也能夠 peekLast()
。其餘的操做例如 offer、poll 一樣相似。
LinkedList 中還有 push()
和 pop()
操做。被當作棧使用時,只須要對頭部節點進行操做就行。
入棧操做:
public void push(E e) {
addFirst(e);
}
複製代碼
出棧操做:
public E pop() {
return removeFirst();
}
複製代碼
LinkedList 中也實現了 ListIterator 和 Spliterator 接口。
因此 LinkedList 也能夠從兩端進行遍歷。在遍歷的時候,一樣也有 fail-fast 機制來檢查遍歷的過程中,容器中的元素是否被修改。
在實現 Spliterator 接口以後,也能夠對容器中的元素進行分段,而後同時讓多個線程同時進行處理,提升處理效率。分割 LinkedList 的代碼以下:
LinkedList<Integer> list = new LinkedList<>();
for (int i = 0; i < 20; i++) {
list.add(i);
}
Spliterator<Integer> splitor = list.spliterator();
Spliterator<Integer> s1 = splitor.trySplit();
Spliterator<Integer> s2 = s1.trySplit();
System.out.println(s1.estimateSize()); // 10
System.out.println(s2.estimateSize()); // 10
複製代碼
(完)
相關文章
關注微信公衆號,聊點其餘的