在分析LinkedList的源碼以前,先看一下ArrayList在數據結構中的位置,常見的數據結構按照邏輯結構跟存儲結構能夠作以下劃分:
node
先看看源碼的註釋:安全
從註釋中能夠看出,LinkedList是一個雙向非循環鏈表,而且實現了Deque接口,仍是一個雙端隊列,因此比ArrayList要複雜一些。bash
在分析LinkedList以前咱們先複習一下鏈表這種數據結構數據結構
鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的。鏈表由一系列結點(鏈表中每個元素稱爲結點)組成,結點能夠在運行時動態生成。每一個結點包括兩個部分:一個是存儲數據元素的數據域,另外一個是存儲下一個結點地址的指針域。函數
鏈表按照指向能夠分爲單向鏈表跟雙向鏈表,也能夠按照是否循環氛分爲循環鏈表跟非循環鏈表。
源碼分析
雙向循環鏈表跟單向循環鏈表能夠進行類比,只是把head節點的pre指針跟tail節點的next指針分別指向tail跟head的數據區域而已。ui
先看一下ArrayList的繼承關係this
跟ArrayList的區別在於LinkedList實現了Deque這個接口,Deque則繼承自Queue這個接口,因此LinkedList可以進行隊列操做,其他的實現跟ArrayList基本同樣,再也不多說,下面開始分析LinkedList的源碼。spa
//序列化
private static final long serialVersionUID = 876323262645176354L;
transient int size = 0;//元素個數
transient Node<E> first;//head結點
transient Node<E> last;//tail節點
//內部類節點
private static class Node<E> {
E item;存儲的數據
Node<E> next;//next指針,指向下一個數據
Node<E> prev;//pre指針,指向上一個數據
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}複製代碼
public LinkedList() {
}複製代碼
當咱們經過此構造方法進行初始化LinkedList的時候,實際上什麼都沒作,此時只有一個Node,data爲null,pre指向null,next也指向null。線程
//調用addAll
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
//緊接着調用addAll(size, c)
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//這個方法比較關鍵,由於不論是初始化,仍是進行添加,都會調用此方法,下面重點分析一下
public boolean addAll(int index, Collection<? extends E> c) {
//檢查index是否合法
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
//初始化兩個Node,保留下一個節點,當集合添加完成以後,須要跟此節點進行鏈接,構成鏈表
Node<E> pred, succ;
//插入的時候就是分兩種,一種是從尾部插入,一種是從中間插入
if (index == size) {
//在尾部插入
succ = null;//null值做爲後面鏈接的一個標誌
pred = last;//將pred指向上一個節點也就是tail節點
} 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
//Node個數>0,當前指針指向新的節點
pred.next = newNode;
//移到下一個節點
pred = newNode;
}
//鏈表添加完畢,開始從斷開的地方進行鏈接
if (succ == null) {
//尾部插入進行鏈接,此時last須要從新賦值,即爲pred節點
last = pred;
} else {
//中間插入,直接講集合的最後一個節點跟以前插入點後的節點進行鏈接就好
pred.next = succ;將當前Node的next指針指向下一個節點
succ.prev = pred;//將下一個節點的pre指向pre
}
size += numNew;
modCount++;
return true;
}複製代碼
結合圖形來理解一下
稍微總結一下,這個addAll實際上就是先把鏈表打斷,而後從斷的左側進行添加一些元素,添加完成以後再將鏈表進行鏈接起來,恩,就是這個樣子,概括一下就是:
經過查看實際上有不少,這裏就不一一貼出來了,最終調用的都是這幾個方法:
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
//將原先的頭結點的pre指針指向新的頭結點
f.prev = newNode;
size++;
modCount++;
}複製代碼
void linkLast(E e) {
//拿到尾節點
final Node<E> l = last;
//初始化一個Node,也就是新的尾節點
final Node<E> newNode = new Node<>(l, e, null);
//將新的尾節點賦值給last
last = newNode;
//尾結點爲空
if (l == null)
//此時只有一個節點,因此當前節點便是頭結點也是尾節點
first = newNode;
else
//將原先的尾節點指向如今的新的尾節點
l.next = newNode;
size++;
modCount++;
}複製代碼
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++;
}複製代碼
有以下幾個方法
跟add操做相對應,也只是改變相應的鏈表的指向而已,咱們選擇一個來看看:
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 {
//將刪除節點的前一個節點的next指向後一個節點
prev.next = next;
x.prev = null;
}
if (next == null) {
//尾結點,刪除以後,尾節點前移
last = prev;
} else {
//將刪除節點的後一個節點的pre指向前一個節點
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}複製代碼
說到底仍是在改變Node節點的指向而已
public E set(int index, E element) {
checkElementIndex(index);//檢查索引
Node<E> x = node(index);//拿到須要修改的那個節點
E oldVal = x.item;//拿到修改的節點的值
x.item = element;//進行修改
return oldVal;
}複製代碼
public E getFirst() {
final Node<E> f = first;//拿到head節點
if (f == null)
throw new NoSuchElementException();
return f.item;
}
public E getLast() {
final Node<E> l = last;////拿到tail節點
if (l == null)
throw new NoSuchElementException();
return l.item;
}
//獲取某一個索引的節點
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
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;
}
}複製代碼
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;
}複製代碼
沒有什麼好說的,就是遍歷查找而已,這裏會發現,LinkedList的查找很低效,須要遍歷整個集合。
public void push(E e) {
addFirst(e);
}複製代碼
public boolean offer(E e) {
return add(e);
}複製代碼
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}複製代碼
public E pop() {
return removeFirst();
}複製代碼
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}複製代碼
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}複製代碼
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}複製代碼
上面都是關於隊列的一些操做,用鏈表也能夠實現,並且操做比較簡單,能夠看作是隊列的一種鏈表實現方式。