List集合就這麼簡單【原始碼剖析】

Java3y發表於2018-04-08

前言

宣告,本文用得是jdk1.8

前一篇已經講了Collection的總覽:Collection總覽,介紹了一些基礎知識。

現在這篇主要講List集合的三個子類:

  • ArrayList
    • 底層資料結構是陣列。執行緒不安全
  • LinkedList
    • 底層資料結構是連結串列。執行緒不安全
  • Vector
    • 底層資料結構是陣列。執行緒安全

這篇主要來看看它們比較重要的方法是如何實現的,需要注意些什麼,最後比較一下哪個時候用哪個~

看這篇文章之前最好是有點資料結構的基礎:Java實現單向連結串列棧和佇列就是這麼簡單二叉樹就這麼簡單

List集合就這麼簡單【原始碼剖析】

List集合就這麼簡單【原始碼剖析】

當然了,如果講得有錯的地方還請大家多多包涵並不吝在評論去指正~

一、ArrayList解析

List集合就這麼簡單【原始碼剖析】

首先,我們來講解的是ArrayList集合,它是我們用得非常非常多的一個集合~

首先,我們來看一下ArrayList的屬性:

List集合就這麼簡單【原始碼剖析】

根據上面我們可以清晰的發現:ArrayList底層其實就是一個陣列,ArrayList中有擴容這麼一個概念,正因為它擴容,所以它能夠實現“動態”增長

1.2構造方法

我們來看看構造方法來印證我們上面說得對不對:

List集合就這麼簡單【原始碼剖析】

1.3Add方法

add方法可以說是ArrayList比較重要的方法了,我們來總覽一下:

List集合就這麼簡單【原始碼剖析】

1.3.1add(E e)

步驟:

  • 檢查是否需要擴容
  • 插入元素

首先,我們來看看這個方法:


    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
複製程式碼

該方法很短,我們可以根據方法名就猜到他是幹了什麼:

  • 確認list容量,嘗試容量加1,看看有無必要
  • 新增元素

接下來我們來看看這個小容量(+1)是否滿足我們的需求:

List集合就這麼簡單【原始碼剖析】

隨後呼叫ensureExplicitCapacity()來確定明確的容量,我們也來看看這個方法是怎麼實現的:

List集合就這麼簡單【原始碼剖析】

所以,接下來看看grow()是怎麼實現的~

List集合就這麼簡單【原始碼剖析】

進去看copyOf()方法:

List集合就這麼簡單【原始碼剖析】

到目前為止,我們就可以知道add(E e)的基本實現了:

  • 首先去檢查一下陣列的容量是否足夠
    • 足夠:直接新增
    • 不足夠:擴容
      • 擴容到原來的1.5倍
      • 第一次擴容後,如果容量還是小於minCapacity,就將容量擴充為minCapacity。

1.3.2add(int index, E element)

步驟:

  • 檢查角標
  • 空間檢查,如果有需要進行擴容
  • 插入元素

我們來看看插入的實現:

List集合就這麼簡單【原始碼剖析】

我們發現,與擴容相關ArrayList的add方法底層其實都是arraycopy()來實現的

看到arraycopy(),我們可以發現:該方法是由C/C++來編寫的,並不是由Java實現:

List集合就這麼簡單【原始碼剖析】

總的來說:arraycopy()還是比較可靠高效的一個方法。

參考R大回答:www.zhihu.com/question/53…

1.4 get方法

  • 檢查角標
  • 返回元素

List集合就這麼簡單【原始碼剖析】




   // 檢查角標
   private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
	
	// 返回元素
    E elementData(int index) {
        return (E) elementData[index];
    }
複製程式碼

1.5 set方法

步驟:

  • 檢查角標
  • 替代元素
  • 返回舊值

List集合就這麼簡單【原始碼剖析】

1.6remove方法

步驟:

  • 檢查角標
  • 刪除元素
  • 計算出需要移動的個數,並移動
  • 設定為null,讓Gc回收

List集合就這麼簡單【原始碼剖析】

1.7細節再說明

  • ArrayList是基於動態陣列實現的,在增刪時候,需要陣列的拷貝複製
  • ArrayList的預設初始化容量是10,每次擴容時候增加原先容量的一半,也就是變為原來的1.5倍
  • 刪除元素時不會減少容量,若希望減少容量則呼叫trimToSize()
  • 它不是執行緒安全的。它能存放null值。

List集合就這麼簡單【原始碼剖析】

參考資料:

二、Vector與ArrayList區別

Vector是jdk1.2的類了,比較老舊的一個集合類。

List集合就這麼簡單【原始碼剖析】

Vector底層也是陣列,與ArrayList最大的區別就是:同步(執行緒安全)

Vector是同步的,我們可以從方法上就可以看得出來~

List集合就這麼簡單【原始碼剖析】

在要求非同步的情況下,我們一般都是使用ArrayList來替代Vector的了~

如果想要ArrayList實現同步,可以使用Collections的方法:List list = Collections.synchronizedList(new ArrayList(...));,就可以實現同步了~

還有另一個區別:

  • ArrayList在底層陣列不夠用時在原來的基礎上擴充套件0.5倍,Vector是擴充套件1倍。

List集合就這麼簡單【原始碼剖析】

List集合就這麼簡單【原始碼剖析】

Vector原始碼的解析可參考:

三、LinkedList解析

List集合就這麼簡單【原始碼剖析】

LinkedList底層是雙向連結串列~如果對於連結串列不熟悉的同學可先看看我的單向連結串列(雙向連結串列的練習我還沒做)【Java實現單向連結串列

理解了單向連結串列,雙向連結串列也就不難了。

List集合就這麼簡單【原始碼剖析】

從結構上,我們還看到了LinkedList實現了Deque介面,因此,我們可以操作LinkedList像操作佇列和棧一樣~

List集合就這麼簡單【原始碼剖析】

LinkedList變數就這麼幾個,因為我們操作單向連結串列的時候也發現了:有了頭結點,其他的資料我們都可以獲取得到了。(雙向連結串列也同理)

List集合就這麼簡單【原始碼剖析】

3.1構造方法

LinkedList的構造方法有兩個:

List集合就這麼簡單【原始碼剖析】

3.2add方法

如果做過連結串列的練習,對於下面的程式碼並不陌生的~

  • add方法實際上就是往連結串列最後新增元素

    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
複製程式碼

3.3remove方法

List集合就這麼簡單【原始碼剖析】

List集合就這麼簡單【原始碼剖析】

實際上就是下面那個圖的操作:

List集合就這麼簡單【原始碼剖析】

3.4get方法

可以看到get方法實現就兩段程式碼:

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
複製程式碼

我們進去看一下具體的實現是怎麼樣的:

List集合就這麼簡單【原始碼剖析】

3.5set方法

set方法和get方法其實差不多,根據下標來判斷是從頭遍歷還是從尾遍歷


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

複製程式碼

......LinkedList的方法比ArrayList的方法多太多了,這裡我就不一一說明了。具體可參考:

四、總結

其實集合的原始碼看起來並不是很困難,遇到問題可以翻一翻,應該是能夠看懂的~

ArrayList、LinkedList、Vector算是在面試題中比較常見的的知識點了。下面我就來做一個簡單的總結:

ArrayList:

  • 底層實現是陣列
  • ArrayList的預設初始化容量是10,每次擴容時候增加原先容量的一半,也就是變為原來的1.5倍
  • 增刪時候,需要陣列的拷貝複製(navite 方法由C/C++實現)

LinkedList:

  • 底層實現是雙向連結串列[雙向連結串列方便實現往前遍歷]

Vector:

  • 底層是陣列,現在已少用,被ArrayList替代,原因有兩個:
    • Vector所有方法都是同步,有效能損失
    • Vector初始length是10 超過length時 以100%比率增長,相比於ArrayList更多消耗記憶體
    • 參考資料:www.zhihu.com/question/31…

總的來說:查詢多用ArrayList,增刪多用LinkedList。

ArrayList增刪慢不是絕對的(在數量大的情況下,已測試):

  • 如果增加元素一直是使用add()(增加到末尾)的話,那是ArrayList要快
  • 一直刪除末尾的元素也是ArrayList要快【不用複製移動位置】
  • 至於如果刪除的是中間的位置的話,還是ArrayList要快

但一般來說:增刪多還是用LinkedList,因為上面的情況是極端的~

img

涵蓋Java後端所有知識點的開源專案(已有6 K star):github.com/ZhongFuChen…

如果大家想要實時關注我更新的文章以及分享的乾貨的話,微信搜尋Java3y

PDF文件的內容均為手打,有任何的不懂都可以直接來問我(公眾號有我的聯絡方式)。

List集合就這麼簡單【原始碼剖析】

List集合就這麼簡單【原始碼剖析】

List集合就這麼簡單【原始碼剖析】

List集合就這麼簡單【原始碼剖析】

相關文章