網上的LinkedList的源碼分析大都爲JDK1.6的版本,所以爲雙向循環鏈表的環形結構。但是JDK1.7的版本爲雙向鏈表。
先簡單分析瞭解下JDK1.6版本的源碼:
參考:
給jdk寫註釋系列之jdk1.6容器(2)-LinkedList源碼解析
這篇博客分析的很徹底!!!贊。
JDK1.6~JDK1.7過程中,LinkedList從雙向循環鏈表轉換爲雙向鏈表
這次來簡單瞭解和分析下JDK1.6的基於雙向循環鏈表的實現:
繼承自List,Deque,Cloneable,Serializible接口
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
雙向循環鏈表的底層數據存儲形式:
private transient Entry header = new Entry(null, null, null);
private transient int size = 0;
其中size爲元素數目,header爲鏈表的頭節點,Entry爲鏈表中的節點對象
經常使用的默認構造方法:
public LinkedList() {
//將header節點的前一節點和後一節點都設置爲自身
header.next = header.previous = header;
}
Entry爲LinkedList的靜態內部類,定義了當前存儲的節點和它相鄰的兩個節點:
private static class Entry {
E element;
Entry next;
Entry previous;
Entry(E element, Entry next, Entry previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}
插入:
/**
* 添加一個集合元素到list中
*/
public boolean addAll(Collection<? extends E> c) {
// 將集合元素添加到list最後的尾部
return addAll(size , c);
}
/**
* 在指定位置添加一個集合元素到list中
*/
public boolean addAll(int index, Collection<? extends E> c) {
// 越界檢查
if (index < 0 || index > size)
throw new IndexOutOfBoundsException( "Index: "+index+
", Size: "+size );
Object[] a = c.toArray();
// 要插入元素的個數
int numNew = a.length ;
if (numNew==0)
return false;
modCount++;
// 找出要插入元素的前後節點
// 獲取要插入index位置的下一個節點,如果index正好是lsit尾部的位置那麼下一個節點就是header,否則需要查找index位置的節點
Entry<E> successor = (index== size ? header : entry(index));
// 獲取要插入index位置的上一個節點,因爲是插入,所以上一個點擊就是未插入前下一個節點的上一個
Entry<E> predecessor = successor. previous;
// 循環插入
for (int i=0; i<numNew; i++) {
// 構造一個節點,確認自身的前後引用
Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
// 將插入位置上一個節點的下一個元素引用指向當前元素(這裏不修改下一個節點的上一個元素引用,是因爲下一個節點隨着循環一直在變)
predecessor. next = e;
// 最後修改插入位置的上一個節點爲自身,這裏主要是爲了下次遍歷後續元素插入在當前節點的後面,確保這些元素本身的順序
predecessor = e;
}
// 遍歷完所有元素,最後修改下一個節點的上一個元素引用爲遍歷的最後一個元素
successor. previous = predecessor;
// 修改計數器
size += numNew;
return true;
}
刪除:
/**
* 刪除第一個匹配的指定元素
*/
public boolean remove(Object o) {
// 遍歷鏈表找到要被刪除的節點
if (o==null) {
for (Entry e = header .next; e != header; e = e.next ) {
if (e.element ==null) {
remove(e);
return true;
}
}
} else {
for (Entry e = header .next; e != header; e = e.next ) {
if (o.equals(e.element )) {
remove(e);
return true;
}
}
}
return false;
}
private E remove(Entry e) {
if (e == header )
throw new NoSuchElementException();
// 被刪除的元素,供返回
E result = e. element;
// 下面修正前後對該節點的引用
// 將該節點的上一個節點的next指向該節點的下一個節點
e. previous.next = e.next;
// 將該節點的下一個節點的previous指向該節點的上一個節點
e. next.previous = e.previous;
// 修正該節點自身的前後引用
e. next = e.previous = null;
// 將自身置空,讓gc可以儘快回收
e. element = null;
// 計數器減一
size--;
modCount++;
return result;
}
查詢:
/**
* 查找指定索引位置的元素
*/
public E get( int index) {
return entry(index).element ;
}
/**
* 返回指定索引位置的節點
*/
private Entry<E> entry( int index) {
// 越界檢查
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException( "Index: "+index+
", Size: "+size );
// 取出頭結點
Entry<E> e = header;
// size>>1右移一位代表除以2,這裏使用簡單的二分方法,判斷index與list的中間位置的距離
if (index < (size >> 1)) {
// 如果index距離list中間位置較近,則從頭部向後遍歷(next)
for (int i = 0; i <= index; i++)
e = e. next;
} else {
// 如果index距離list中間位置較遠,則從頭部向前遍歷(previous)
for (int i = size; i > index; i--)
e = e. previous;
}
return e;
}
改動:
/**
* 修改指定位置索引位置的元素
*/
public E set( int index, E element) {
// 查找index位置的節點
Entry e = entry(index);
// 取出該節點的元素,供返回使用
E oldVal = e. element;
// 用新元素替換舊元素
e. element = element;
// 返回舊元素
return oldVal;
}