學透 LinkedList 底層實現原理,狂虐面試官!

前言:面試時被問到 LinkedList 的底層實現,瞬間啞口無言,所以決定狠狠地啃一下 Java源碼,目標就是吊打面試官!
好了,廢話不多說,進入正題。

簡單介紹

很多人聽到 LinkedList,應該就能猜到它的底層是用鏈表實現的,更準確來說,是用 雙向鏈表 實現的,和常用的 ArrayList 不同,LinkedList 更適用於插入和刪除操作較多的場景,而且LinkedList 實現了 Queue 接口,所以經常被當做隊列來使用。

那麼 LinkedList 底層到底是怎麼實現的呢?

LinkedList 的成員變量

俗話說,學習一個類,就要先從這個類包含的屬性入手,那麼我們就來看看吧!

LinkedList 的主要變量:

transient int size = 0;		// 雙向鏈表的節點數量
transient Node<E> first;	// 雙向鏈表的第一個節點
transient Node<E> last;		// 雙向鏈表的最後一個節點

另外,LinkedList 內部結構是雙向鏈表,作爲鏈表的組成,必然少不了節點類 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;
	}
}

注意,看到這裏我們就要思考一下,LinkedList 的主要操作是怎麼實現的?
我們不妨來大膽猜想一下:

  • add(E e) :將指定元素添加到鏈表末尾。讓鏈表的最後一個節點的 next 指向新增節點 e,讓新增節點的 prev 指向最後一個節點,最後將 last 指向新增節點,size+1。
  • get(int index):返回鏈表中指定位置的元素。檢查下標是否越界,如果越界,拋異常;沒越界,則從頭或者從尾部開始遍歷(指定節點離哪個近就從哪開始),找到指定節點後返回節點值。
  • peek():檢索但不刪除此鏈表的第一個元素。若 first 爲空,返回 null,否則返回 first 節點值。
  • poll() :檢索並刪除此鏈表的第一個元素。若 first 爲空,返回 null,否則返回 first 節點值,並刪除 frist 節點。
  • remove(Object o) :從鏈表中刪除第一次出現的指定元素。若 o 爲空,遍歷鏈表找到值爲 null 的節點並刪除;否則,遍歷鏈表找到值等於 o 的節點值 的節點,並刪除。
  • size() :返回鏈表的元素數量。直接返回 size。

--------------------------------------------- 想狂虐面試官?請往下分析源碼 ---------------------------------------------

LinkedList 的主要方法

接下來將分析 LinkedList 幾個主要方法的源碼,不要緊張,可以先放鬆一下,深吸一口氣,其實閱讀源碼並沒有那麼難,只要掌握正確的方法和節奏,看我來簡單地分析給你聽:

1.add 方法

有多種不同的add()方法,我們這裏分析一下比較常用的 add(E e):

注:代碼中的中文註釋是筆者添加的
public boolean add(E e) {
	// 將 e 添加到鏈表尾部
	linkLast(e);
	return true;
}

void linkLast(E e) {
	// 尾節點
	final Node<E> l = last;
	// 新增節點初始化,prev 指向 尾節點
	final Node<E> newNode = new Node<>(l, e, null);
	// last 指向 新增節點
	last = newNode;
	// 舊鏈表尾節點 爲空
	if (l == null)
		first = newNode;
	// 不爲空
	else
		l.next = newNode;
	// 鏈表節點數量 + 1
	size++;
	modCount++;
}

總結
調用 add(E e) 方法,先初始化該新增節點,並將 prev 指向舊鏈表的最後一個節點,若舊鏈表最後一個節點爲空(即鏈表爲空),first 和 last 都指向新增節點,最後讓 size +1,鏈表操作次數 modCount+1。

2.get 方法

LinkedList 中的 get 方法爲 get(int index) ,那我們就來看一下源碼:

public E get(int index) {
	// 檢查下標是否越界,越界則拋異常
	checkElementIndex(index);
	// 獲取指定下標節點的節點值
	return node(index).item;
}

private void checkElementIndex(int index) {
	if (!isElementIndex(index))
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private boolean isElementIndex(int index) {
	return index >= 0 && index < size;
}

Node<E> node(int 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;
	}
}

總結
調用 get(int index) 方法,先判斷下標 index 是否越界,越界則拋異常;沒越界則根據下標選擇從頭節點或者從尾節點開始遍歷,找到指定下標節點,則返回該節點的節點值。

3.poll 方法

peek() 方法和 poll() 方法都是檢索鏈表的頭節點,但是 poll() 檢索後要刪除該節點,比較複雜,所以我們只分析 poll()。

public E poll() {
	// 頭節點爲空,返回 null, 否則刪除頭節點並返回
	final Node<E> f = first;
	return (f == null) ? null : unlinkFirst(f);
}

// 刪除頭節點並返回
private E unlinkFirst(Node<E> f) {
	// 獲取頭節點元素值
	final E element = f.item;
	// 獲取第二個節點
	final Node<E> next = f.next;
	// 頭節點元素值 置空
	f.item = null;
	// 頭節點 next 置空, 後期 JVM 會進行 GC 回收
	f.next = null; // help GC
	// first 指向 第二個節點
	first = next;
	// 第二個節點爲空
	if (next == null)
		last = null;
	// 第二個節點不爲空
	else
		next.prev = null;
	// 鏈表節點數量 + 1
	size--;
	modCount++;
	return element;
}

總結
調用 poll() 方法,先判斷頭節點是否爲空,爲空則返回 null;不爲空則將 first 指向 第二個節點後,返回頭節點的值,並刪除頭節點。

4.remove 方法

LinkedList 的 remove 方法也有很多個,我們來分析常用的 remove(Object o) 方法(刪除第一次出現指定元素的節點)。

public boolean remove(Object o) {
	// 若 o 爲空
	if (o == null) {
		// 遍歷找到值爲 null 的節點並刪除
	    for (Node<E> x = first; x != null; x = x.next) {
	        if (x.item == null) {
	            unlink(x);
	            return true;
	        }
	    }
	// 若 o 不爲空
	} else {
		// 遍歷找到值爲 x的值 的節點並刪除
	    for (Node<E> x = first; x != null; x = x.next) {
	        if (o.equals(x.item)) {
	            unlink(x);
	            return true;
	        }
	    }
	}
	return false;
}

//刪除 x 節點
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 {
	    prev.next = next;
	    x.prev = null;
	}
	if (next == null) {
	    last = prev;
	} else {
	    next.prev = prev;
	    x.next = null;
	}
	x.item = null;
	size--;
	modCount++;
	return element;
}

總結
調用 remove(Object o),若 o 爲空,則遍歷鏈表找到值爲 null 的節點並刪除;若 o 不爲空,則遍歷鏈表找到值爲 o的值 的節點並刪除。

分析結束啦!謝謝觀看~

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