JDK源碼閱讀之LinkedList

LinkedList

      當比較LinkedList和ArrayList的區別時我們也許知道前者底層實現是鏈表,後者底層實現是數組,對於ArrayList在【此文】中詳細介紹了,但是對於LinkedList的理解僅僅侷限在鏈表而已,現在一起來看看它的底層實現吧!

類圖

LinkedList類圖

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

      我們發現LinkedList實現了Deque接口,它提供了對隊列操作的相關方法。所以我們經常使用LinkedList去完成隊列相關操作。
Deque

類前註釋

      雙向鏈表實現了ListDqeue接口,它實現了所有的可選列表操作,它允許所有的元素類型,包括null

      注意:LinkedList是非線程安全的。如果多個線程同時訪問鏈接列表,並且至少有一個線程在結構上修改列表,則必須在外部進行同步。 (結構修改是添加或刪除一個或多個元素的任何操作;僅設置元素的值不是結構修改。)這通常通過在自然封裝列表的對象上進行同步來實現。 如果沒有這樣的對象存在,列表應該使用Collections.synchronizedList方法“包裝”。 這最好在創建時完成,以防止意外的不同步訪問列表。

      LinkedList的iterator和listIterator方法返回的迭代器是fail-fast的:如果列表在迭代器創建之後的任何時間被結構化地修改,除了通過迭代器自己的remove或add方法之外,迭代器將會拋出ConcurrentModificationException 因此,面對併發修改,迭代器將快速而乾淨地失敗,而不是在未來未確定的時間冒着任意的非確定性行爲。

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;
	}
}

      看到nextprev就知道LinkedList不是單鏈表,是雙向鏈表

成員變量

transient int size = 0;    //記錄LinkedList的大小
transient Node<E> first;   //表示LinkedList的頭節點。
transient Node<E> last;    //表示LinkedList的尾節點。

構造方法

public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
	this();
	addAll(c);
}

      LinkedList提供兩個構造方法,其中需要注意的是第二個,它接受一個Collection來完成初始化。關鍵在於addAll方法。

public boolean addAll(Collection<? extends E> c) {
	//addAll方法可以在指定索引的節點後面插入Collection中的元素,初始化時size爲0,即從頭開始插入
	return addAll(size, c);
}

public boolean addAll(int index, Collection<? extends E> c) {
	//檢查index是否合法,判斷規則:index >= 0 && index <= size;
	//不合法拋出IndexOutOfBoundsException
	checkPositionIndex(index);

	//將Collection轉爲數組
	Object[] a = c.toArray();
	int numNew = a.length;
	//插入的集合爲空,直接返回false
	if (numNew == 0)
		return false;
	
	//pred:index索引節點的前驅
	//succ:index索引表示的節點
	Node<E> pred, succ;
	if (index == size) {
		//在最末端插入節點
		succ = null;
		pred = last;
	} else {
		//添加位置在鏈表中
		//node()方法用於尋找index索引表示的節點
		succ = node(index);
		pred = succ.prev;
	}

last和prev

last和prev

	//遍歷數組,創建節點進行插入操作
	for (Object o : a) {
		@SuppressWarnings("unchecked") E e = (E) o;
		//①、初始化新節點
		Node<E> newNode = new Node<>(pred, e, null);
		//②、設置前驅的後繼
		if (pred == null)
			//pred爲null說明鏈表中無節點
			first = newNode;
		else
			pred.next = newNode;//設置前驅的後繼
		//③、更新前驅
		pred = newNode;
	}

	//④、鏈接成完整鏈表
	if (succ == null) {
		last = pred;
	} else {
		pred.next = succ;
		succ.prev = pred;
	}
	//更新鏈表的size
	size += numNew;
	modCount++;
	return true;
}

完整插入示意圖

linkFirst

      私有方法,插入一個元素,並將它置爲頭節點。

private void linkFirst(E e) {
	//備份頭節點
	final Node<E> f = first;
	//創建新節點
	final Node<E> newNode = new Node<>(null, e, f);
	//將新節點設置爲頭節點
	first = newNode;
	if (f == null)
		//f爲空說明鏈表爲空,插入的這個節點也是尾節點
		last = newNode;
	else
		//設置原來頭節點的前驅爲新節點
		f.prev = newNode;
	size++;
	modCount++;
}

linkLast

      插入一個元素,並將它置爲尾節點。

void linkLast(E e) {
	//備份尾節點
	final Node<E> l = last;
	//創建新節點
	final Node<E> newNode = new Node<>(l, e, null);
	//將新節點設置爲尾節點
	last = newNode;
	if (l == null)
		//f爲空說明鏈表爲空,插入的這個節點也是頭節點
		first = newNode;
	else
		//設置原來尾節點的後繼爲新節點
		l.next = newNode;
	size++;
	modCount++;
}

getFirst

      重寫來自Deque的方法,返回頭節點,如果頭節點爲null拋出異常。

public E getFirst() {
	final Node<E> f = first;
	if (f == null)
		throw new NoSuchElementException();
	return f.item;
}

getLast

      重寫來自Deque的方法,返回尾節點,如果尾節點爲null拋出異常。

public E getLast() {
	final Node<E> l = last;
	if (l == null)
		throw new NoSuchElementException();
	return l.item;
}

removeFirst

      重寫來自Deque的方法,刪除頭節點並返回舊值,如果頭節點爲null拋出異常。

public E removeFirst() {
	final Node<E> f = first;
	if (f == null)
		throw new NoSuchElementException();
	return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {
	// 斷言 f == first && f != null;
	final E element = f.item;
	final Node<E> next = f.next;
	f.item = null;//將數據域和後繼設爲null,GC幫助銷燬
	f.next = null; // help GC
	//重新設置頭節點
	first = next;
	if (next == null)
		last = null;
	else
		next.prev = null;
	size--;
	modCount++;
	return element;
}

removeLast

      重寫來自Deque的方法,刪除尾節點並返回舊值,如果尾節點爲null拋出異常。

remove

      刪除等於指定元素的第一次出現的節點。刪除成功返回true,失敗返回false

addFirst

      重寫來自Deque的方法,從頭部添加節點。藉助linkFirst方法完成此方法。

addLast

      重寫來自Deque的方法,從尾部添加節點。藉助linkLast方法完成此方法。

add

      往鏈表中添加節點,從尾部插入。offer方法調用此方法實現尾插功能。

clear

      清空整個鏈表。其實就是遍歷整個鏈表,將數據域,前驅,後繼置爲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++;
}

get

      獲取指定索引的元素值。

public E get(int index) {
	//判斷索引的合法性
	checkElementIndex(index);
	return node(index).item;
}

Node<E> node(int index) {
	//如果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;
	}
}

set

      爲指定索引的節點設置值,返回舊值。

public E set(int index, E element) {
	checkElementIndex(index);
	Node<E> x = node(index);
	E oldVal = x.item;
	x.item = element;
	return oldVal;
}

indexOf

      返回此列表中指定元素的第一次出現的索引,如果此列表不包含元素,則返回-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;
}

      與indexOf相似的還有lastIndexOf這一方法,它返回此列表中指定元素的最後一次出現的索引,如果此列表不包含元素,則返回-1。

contains

      查找鏈表中是否有知道的元素值,返回對應的索引(第一次出現的位置),使用indexOf實現此方法。

public boolean contains(Object o) {
	return indexOf(o) != -1;
}

peek

      返回頭節點的值,如果頭節點爲null則返回null。

public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

      與之類似的方法還有element,但是不同的是如果鏈表爲空則拋出NoSuchElementException

peekLast

      返回尾節點的值,如果頭節點爲null則返回null。

poll

public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

      出隊操作,返回隊首元素值。

push和pop

      push方法從頭部插入元素,pop從頭部刪除元素。常使用這兩個方法實現棧操作。

迭代器

      LinkedList爲我們提供了兩種迭代器,分別爲ListIteratorDescendingIterator。使用ListIterator迭代器我們可以完成遍歷,刪除和修改節點的操作,同時這也是官方推薦我們遍歷鏈表的方式,而不是使用fori遍歷鏈表。使用ListIterator我們不僅可以正向遍歷鏈表也可以反向遍歷。
      descendingIterator方法來自Deque接口,使用DescendingIterator我們只能從後向前遍歷,並且只提供刪除操作,無法添加和修改節點。

clone

      同ArrayList一樣,LinkedList的clone方法實現的是淺拷貝。

toArray

      此方法將鏈表中的值放在數組中,並將數組返回。

public Object[] toArray() {
	Object[] result = new Object[size];
	int i = 0;
	for (Node<E> x = first; x != null; x = x.next)
		result[i++] = x.item;
	return result;
}

      使用此方法不能講Object[]強轉爲其他類型,否則拋ClassCastException異常。如果我們需要實現將鏈表值放在指定類型數組中使用toArray(T[] a)方法。不過這裏有個小坑哦!

public <T> T[] toArray(T[] a) {
	//如果你傳入的數組a不夠大會新建一個數組對象賦給a,
	//後序會將值賦值給新創建的數組對象,那麼原來的那個數組將沒有值,全爲null
	//所以我們還是統一使用返回的數組就不會錯了
	if (a.length < size)
		a = (T[])java.lang.reflect.Array.newInstance(
							a.getClass().getComponentType(), size);
	int i = 0;
	Object[] result = a;
	for (Node<E> x = first; x != null; x = x.next)
		result[i++] = x.item;

	if (a.length > size)
		a[size] = null;

	return a;
}

文章同步【個人站】

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