面試官:你給我講一下LinkedList源碼吧!網友:這不是章口就來嗎?

一.LinkedList數據結構

1.1 數據結構

LinkedList 底層數據結構是一個雙向鏈表,總體結構以下圖所示:
node

注意事項:
  • 鏈表每一個節點叫作 Node,Node 有 prev 屬性,表明前一個節點的位置,next 屬性,表明後一個節點的位 置
  • first 是雙向鏈表的頭節點,它的前一個節點是 null。
  • last 是雙向鏈表的尾節點,它的後一個節點是 null;
  • 當鏈表中沒有數據時,first 和 last 是同一個節點,先後指向都是 null;
  • 由於是個雙向鏈表,是沒有大小限制的。

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

二.源碼分析

2.1 LinkedList類註釋解析

  • 使用「雙向鏈表」來實現List與Deque接口。 實現了全部List接口中的方法,而且容許存放全部元素,包括Null。
  • 全部的操做均可經過雙向鏈表完成。經過從開頭或者結尾遍歷集合,去接近要操做的那個元素。
  • 是非線程安全的,多線程狀況下,推薦使用線程安全類:Collections#synchronizedList
  • 加強 for 循環,或者使用迭代器迭代過程當中,若是數組大小被改變,會快速失敗,拋出異常。

2.2 新增

源碼解析:

​ 新增節點時,咱們能夠選擇追加到鏈表頭部,仍是追加到鏈表尾部,add 方法默認是從尾部開始追加,addFirst 方面試

法是從頭部開始追加:算法

添加:
/**
     * 將元素添加到鏈表尾部,等於addFirst 
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
從尾部增長:
/**
     *  從尾部開始追加節點
     */
    void linkLast(E e) {
        // 把尾節點數據暫存
        final Node<E> l = last;
        // 新建新的節點,初始化入參含義: 
        // l 是新節點的前一個節點,當前值是尾節點值 
        // e 表示當前新增節點,當前新增節點後一個節點是 null
        final Node<E> newNode = new Node<>(l, e, null);
        // 將新建節點追加到尾部
        last = newNode;
        //若是鏈表爲空 l 是尾節點,尾節點爲空,鏈表即空,頭部和尾部是同一個節點,都是新建的節點
        if (l == null)
            first = newNode;
        else
        //不然把前尾節點的下一個節點,指向當前尾節點。
            l.next = newNode;
        //大小和版本更改
        size++;
        modCount++;
    }
從頭部增長:
/**
     *從頭部開始追加節點
     */
    private void linkFirst(E e) {
         // 把頭節點數據暫存
        final Node<E> f = first;
        // 新建新的節點,初始化入參含義: 
        // l 是新節點的前一個節點,當前值是尾節點值 
        // f 表示當前新增節點,當前新增節點後一個節點是 null
        final Node<E> newNode = new Node<>(null, e, f);
         // 將新建節點追加到頭部
        first = newNode;
        //若是鏈表爲空 f 是頭節點,頭節點爲空,鏈表即空,頭部和尾部是同一個節點,都是新建的節點
        if (f == null)
            last = newNode;
        else
        //上一個頭節點的前一個節點指向當前節點
            f.prev = newNode;
        size++;
        modCount++;
    }

注意事項:

  • 頭部追加節點和尾部追加節點,只是前者是移動頭節點的 prev 指向,後者是移動尾節點的 next 指向,二者區別不大。

2.3 刪除實現

源碼解析:

LinkedList節點刪除的方式和追加相似,咱們能夠選擇從頭部刪除,也能夠選擇從尾部刪除,刪除操做會把節點的值,先後指spring

向節點都置爲 null。數據庫

從頭部刪除:
/**
     * 從頭部開始刪除節點
     */
    private E unlinkFirst(Node<E> f) {
        //拿出頭節點的值,做爲方法的返回值
        final E element = f.item;
        // 拿出頭節點的下一個節點
        final Node<E> next = f.next;
        //幫助 GC 回收頭節點
        f.item = null;
        f.next = null; 
        //頭節點的下一個節點成爲頭節點
        first = next;
        //若是 next 爲空,代表鏈表爲空
        if (next == null)
            last = null;
        else
        //鏈表不爲空,頭節點的前一個節點指向 null
            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;
}

注意事項:

  • 先後指向節點都置爲 null,是爲了幫助 GC 進行回收;
  • 從源碼中咱們能夠了解到,鏈表結構的節點新增、刪除僅僅把先後節點的指向修改了, 因此LinkedList新增和刪除度很快。

2.4 實現查詢

查詢:
/**
 * 根據鏈表索引位置查詢節點
 */
Node<E> node(int index) {
    //  若是 index 處於隊列的前半部分,從頭開始找,size >> 1 是 size 除以 2 的意思。
    if (index < (size >> 1)) {
        Node<E> x = first;
   // 直到 for 循環到 index 的前一個 node 中止
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
      // 若是 index 處於隊列的後半部分,從尾開始找
        Node<E> x = last;
      // 直到 for 循環到 index 的後一個 node 中止
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
注意事項:

​ LinkedList 並無採用從頭循環到尾的作法,而是採起了二分法,首先看 index 是在鏈表的前半部分,仍是後半部分。若是是前半部分,就從頭開始尋找,反之亦然。經過這種方式,使循環的次數 至少下降了一半,提升了查找的性能。設計模式

三.時間複雜度

get() 獲取第幾個元素,依次遍歷,複雜度O(n)
add(E) 添加到末尾,複雜度O(1)
add(index, E) 添加第幾個元素後,須要先查找到第幾個元素,直接指針指向操做,複雜度O(n) (這個比較容易想錯)
remove()刪除元素,直接指針指向操做,複雜度O(1)數組

四.線程安全

4.1 線程安全問題

只有當 LinkedList做爲共享變量時,纔會有線程安全問題,當 LinkedList是方法內的局部變量時,是沒有線程安全的問題的。緩存

LinkedList有線程安全問題的緣由,是由於 LinkedList自身的 size、modConut 在進行各類操做時,都沒有加鎖,並且這些變量的類型並不是是可見(volatile)的,因此若是多個線程對這些變量進行操做時,可能會有值被覆蓋的狀況。安全

類註釋中推薦使用 Collections#synchronizedList 來保證線程安全,SynchronizedList 是經過在每一個方法上面加上鎖來實現,雖然實現了線程安全,可是性能大大下降,具體實現源碼:數據結構

public boolean add(E e) {
    synchronized (mutex) {return c.add(e);}
}

咱們也可使用ConcurrentLinkedQueue來保證線程安全,

五.總結

LinkedList的底層是鏈表結構 ,適用於適合於常常新增和刪除的場景, 歡迎關注公衆號:前程有光,領取一線大廠Java面試題總結+各知識點學習思惟導+一份300頁pdf文檔的Java核心知識點總結! 這些資料的內容都是面試時面試官必問的知識點,篇章包括了不少知識點,其中包括了有基礎知識、Java集合、JVM、多線程併發、spring原理、微服務、Netty 與RPC 、Kafka、日記、設計模式、Java算法、數據庫、Zookeeper、分佈式緩存、數據結構等等。

相關文章
相關標籤/搜索