前言
上一篇文章對ArrayList的源碼進行了一個簡單的解析,那麼本篇將對ArrayList的“兄弟”集合類LinkedList進行解析,相對於ArrayList而言,LinkedList的源碼相對複雜一點點,但還沒難道難以閱讀的地步,稍微用點心還是可以滴。
那麼說到LinkedList,在面試的時候都會問他的數據結構是什麼,時間複雜度是什麼等等。有時候一些問題是成鏈式結構的,譬如LinkedList的時間複雜度是由其數據結構決定的,當你瞭解了它的數據結構,後面相應的問題也都可以解決了。
一、LinkedList的基本實現
在解析之前,先來看下LinkedList類的UML類圖:
上圖很清晰的展示了LinkedList的類的繼承結構,那我們來做個簡單的分析。
實現:
1)List接口:具有list接口中的相關功能
2)Deque:繼承了Deque接口,說明了LinkedList具有雙向隊列的功能
3)Cloneable:說明LinkedList是具有克隆的功能
4)Serializable:說明其可被序列化
繼承:
AbstractSequentialList:LinkedList=> AbstractSequentialList=>AbstractList=> AbstractCollection—> Collection => Iterable
二、LinkedList的基本信息
下面代碼中是LinkedList之後基本屬性和內部類:
// LinkedList大小
transient int size = 0;
// 頭節點
transient Node<E> first;
// 尾結點
transient Node<E> last;
// linkedList內部類,用來存儲集合中的元素
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內的構造方法:
// 無參構造
public LinkedList() {
}
// 有參構造,傳入一個集合作爲參數來初始化
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;
// 如果需要添加的數組長度爲0,則直接返回
if (numNew == 0)
return false;
// 定義兩個節點,pred爲待插入節點的前一個節點,succ爲待添加節點的位置
Node<E> pred, succ;
// 如果index == size,則把succ置爲null,pred指向尾結點
// 反之,則把succ插入待插入的位置
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
// 遍歷數組,沒遍歷一次新建一個節點,然後存入a中的數據
// 如果帶插入節點的前一個節點爲空,則把待插入的節點設爲首節點
// 否則插入當前節點的後面,然後再指向前一個節點,形成雙向鏈表
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;
}
// 如果succ==null,把添加的新節點指向最後一個位置,last指向pred
// 當不爲空的時候,則把pred的next指向succ,succ的prev指向pred
// 然後把集合的大小設置爲新的大小
// modCount 自增,爲修改的次數
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
三、添加方法
// 添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
// 新添加元素在尾結點
void linkLast(E e) {
// 把last指向l
final Node<E> l = last;
// 新建節點,用於存儲元素,然後設置自身爲尾結點
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
// 如果l爲null,則則說明原來數據就爲空,那麼就把新節點設爲頭節點,反之則把l的next指向新節點
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
// 添加到指定位置
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
// 如果index等於size,則繼續從尾部添加
linkLast(element);
else
// 在非空節點前插入數據
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
// 新建一個變量,把succ指向新建節點的前一個節點
final Node<E> pred = succ.prev;
// 新建節點,把它的前直接點prev設置爲剛纔新建的變量,後置節點設置爲succ
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
// 如果pred爲null,則把新節點設爲頭節點,反之,則把succ的前一個節點的next設爲新節點
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
四、刪除方法
// 根據傳入對象進行刪除
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;
}
// 根據index來刪除數據
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
// 刪除首節點數據
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
// 刪除尾結點數據
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
具體調用方法實現:
//
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;
// 如果爲null,則說明是頭節點,把next置爲first
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
// 如果爲null,則說明是最後一個節點,把prev置爲last
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
// 刪除第一個元素
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
// 先定義一個變量,用來指向待刪除的節點的值,再定義一個變量指向待刪除的節點的下一個節點
final E element = f.item;
final Node<E> next = f.next;
// 把f的值和next置爲null
f.item = null;
f.next = null; // help GC
first = next;
// 判斷它的下一個節點是否爲null,如果爲null,則需要把last設置爲null
// 否則需要把next的prev設置爲null,因爲next現在指代頭節點
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
// 所實現的思路和上面的方法差不多
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;
}
五、修改方法
// 首先檢查索引是否合法,然後通過index獲取舊值,然後把新值替換即可,最後把舊值返回去
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
六、修改方法
// 1、按照 index來刪除元素,這裏並沒有從開頭曲遍歷刪除,而是先判斷index更靠近頭節點還是尾結點
// 然後再進行遍歷刪除
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;
}
}
// 2、根據傳入對象刪除數據,主要調用indexOf(o)方法來判斷元素的位置
public boolean contains(Object o) {
return indexOf(o) != -1;
}
// 首先判斷對象是否爲空,然後遍歷node,在判斷是否包含對象中的元素,
// 如果有,則返回對應的位置index,如果找不到,則返回-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底層是一個雙向鏈表,且是一個直線型的鏈表結構。
- LinkedList 進行節點插入、刪除卻要高效得多,但是隨機訪問性能則要比動態數組慢
- LinkedHashSet,內部構建了一個記錄插入順序的雙向鏈表,因此提供了按照插入順序遍歷的能力,與此同時,也保證了常數時間的添加、刪除、包含等操作,這些操作性能略低於 HashSet,因爲需要維護鏈表的開銷。
- 最後建議童鞋們 能自己動手寫一個簡單的LinkedList