java集合深入理解(三):ArrayList、Vector、LinkedList的底層源碼分析和對比

(一)List接口的概述

在前面我們講完了Collection的特點和使用,接下來就開始講Collection的子接口和實現類,List具有以下兩個特點:

1.有序(不是指按數值大小有序排列,而是指插入和取出的順序是固定的),因爲List通過下標記錄值

2.可重複,List可以添加重複的值,也可以添加重複的空值

List繼承了Collection,所以Collection中的方法它都能使用,當然List也有屬於自己的特有方法,下面就來介紹一下。

(二)List的特有方法

public class ListTest1 {
    public static void main(String[] args) {
        List list = new ArrayList();
        //特有方法1.add(index,element)
        list.add(0,"a");
        list.add(1,"a");
        list.add(2,null);
        list.add(3,"b");
        System.out.println(list);
        //特有方法2.list.get(index)
        System.out.println(list.get(0));
        //特有方法3.list.indexOf(element) 查找第一個element的索引
        System.out.println(list.indexOf("a"));
        //特有方法4.list.remove(index)
        list.remove(0);
        System.out.println(list);
        //特有方法5.list.set(index,element)
        list.set(0,"d");
        System.out.println(list);
    }
}

因爲List是繼承Collection的,因此迭代器遍歷和增強for遍歷都適用於List,同時因爲List存在下標,因此也可以像遍歷數組一樣遍歷LIst。

(三)ArrayList的底層和源碼分析

集合的使用並不難,因此我們有必要去理解集合底層的原理和看它的部分代碼。

Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)

以上這一段是ArrayList的官方文檔介紹,來自Java Platform SE 8。這段文字的意思翻譯過來就是指ArrayList是個可變數組,擁有了List的所有操作,運行任何元素,包括null。它還有屬於自己的一些方法。ArrayList和Vector很相似,但是ArrayList是unsynchronized,即它不是線程同步的。

3.1 底層結構

我們已經知道了ArrayList的底層就是一個數組,在JDK8中:當初始化時,ArrayList中會初始化一個Object數組,容量爲0;當要添加數據時,初始容量變爲10,當第11個元素要進來時,容量擴容爲15,當第16個元素要進來時,容量擴容爲22。當每次要擴容時,都會擴容成原來容量的1.5倍。下面通過調試來分析一下部分源碼:

3.1.1 首先寫一個簡單的arrayList添加元素的代碼

public class ListTest2 {
    public static void main(String[] args) {
        ArrayList list=new ArrayList();
        for (int i=1;i<35;i++){
            list.add(i);
        }
    }
}

將斷點設置在初始化ArrayList的位置,進入ArrayList構造方法,初始化時,ArrayList內部數組的容量被設置爲0

public ArrayList(){
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

接着進入add方法:

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

add方法並不複雜,首先進入ensureCapacityInternal()方法判斷容量是否足夠,容量足夠就添加數據,進入ensureCapacityInternal方法:

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

當第一次添加數據時,minCapacity=0,滿足if語句的條件,則通過Math.max方法設置第一次擴容的minCapacity=10,進入ensureExplicitCapacity()方法

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

此時minCapacity=10,elementData.length=0,首先對操作數加1,接着判斷minCapacity於elementData長度的差值,大於0(即minCapacity容量不夠用時)則進入grow函數

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

grow函數用於對數組進行擴容,(oldCapacity >> 1)相當於(oldCapacity / 2)。第一次進入時minCapacity=10,oldCapacity =0,所以第四行的運算結果還是等於0,新的容量就變爲10。第二次進入時(即minCapacity=11),newCapacity 就等於15,也就是我們之前所講的1.5倍。

(四)Vector與ArrayList的對比

vector目前已經很少用了,因此我們不過多介紹,主要說一下它和ArrayList的區別

Vector和ArrayList很像,底層也是一個數組,和ArrayList不同的是,Vector是線程安全的,它使用了synchronized關鍵詞:

在擴容倍數上,ArrayList是1.5倍,而Vector是2倍,下面是Vector的grow函數源碼:

第260行的代碼展示了擴容操作。

(五)LinkedList的底層和源碼分析

Doubly-linked list implementation of the List and Deque interfaces. Implements all optional list operations, and permits all elements (including null).

LinkedList的底層結構是一個雙向鏈表,擁有List的所有操作。

首先是兩個重要的元素first和last,分別指向首結點和尾結點

另外每個節點(Node)也有三個屬性:item、next、prev分別表示當前元素,前一個和後一個。

我們還是通過單步調試去看看LinkedList的內部執行邏輯,首先寫一條簡單的代碼:

public class LinkedList1 {
    public static void main(String[] args) {
        LinkedList linkedList=new LinkedList();
        for (int i=1;i<10;i++){
            linkedList.add(i);
        }
    }
}

初始化的構造方法不包含其他的代碼,直接進入add方法:

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

然後再進入linkLast方法:

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

這段代碼也容易理解,先設置l指向當前節點,再創建新節點,前指向l,後指向null。再根據是否是第一個插入的數據完成雙鏈表的連接。

(六)三個實現類的對比

通過兩個表格來表示三者之前的差距:

 

底層結構

版本

線程安全

效率

擴容倍數

ArrayList

可變數組

JDK1.2

不安全

1.5

Vector

可變數組

JDK1.0

安全

2.0

LinkedList

雙向鏈表

JDK1.2

不安全

增刪高,改查低

不需要擴容

如何選擇:

如果考慮線程安全問題:Vector

不考慮線程安全:

    增刪多:LinkedList

    改查多:ArrayList

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