1. 程式人生 > >ArrayList源碼解析

ArrayList源碼解析

3.4 比較 empty 集合 了解 all iou obj row

1. 引言

上個月去一家公司面試 java 實習生,面試官說的一句話我記得很清楚 作為一個java 工程師,你不去看源碼是很難提高的。通過看源碼,不僅可以更快的解決問題,而且可以直接接觸到大牛寫的代碼,了解他們的設計思想,是有很大的好處的。 雖然後面由於種種原因把實習給辭了,不過面試官給的建議我認為是有用的,從今天開始了解一下 JDK 中集合框架的源碼,以此提升自己。

查看 JDK 版本為 1.8。另外,由於本人只是水平有限,因此博客中有問題歡迎大家指出。

2. 概要

ArrayList 底層的數據結構很簡單,就是利用數組來完成的。在源碼中是這樣定義的:transient Object[] elementData;

,根據構造函數的不同,其初始化方式也不同。其默認大小為 10。

這裏為什麽要使用 transient 關鍵字修飾呢?通過網上查找資料了解到,elementData 數組中的元素並不總是滿的,例如數組長度為 10 而存儲的元素只有五個。ArrayList 自定義了自己的 wirteObject 方法,保證只有這五個元素被序列化。

還需要註意的一點是,ArrayList 不是線程安全的,在多線程條件下,可以考慮使用 Collections.synchronizedList(List list) 來返回一個線程安全的 ArrayList。也可以使用 concurrent 包下的 CopyOnWriteArrayList

類。

3. 常用 API

3.1 擴容

ArrayList 底層雖然是使用數組存儲數據,但我們並不需要擔心會發生數組越界的情況。每次添加元素時,內部都會執行一個方法 ensureCapacityInternal(int minCapacity) ,傳入的參數為當前數組長度+1,即現在 ArrayList 需要的最小容量,判斷添加一個元素之後是否會存在數組越界的情況。

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

ensureExplicitCapacity 函數中,比較 ArrayList 需要的最小容量和當前數組的長度大小,判斷是否進行擴容,如果需要的話,則調用 grow 方法。通過查看源代碼,發現,它擴容後的大小一般為原數組的 1.5 倍。當然數組的長度也是有限的,它的最大長度為 Integer.MAX_VALUE - 8

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

3.2 添加元素

了解了 ArrayList 的擴容過程,那麽添加元素這個函數就變得簡單了。其實現如下,其中 size 表示當前 ArrayList 元素的個數,添加元素直接對下標為 index 的位置賦值。

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

3.3 刪除元素

在 ArrayList 中,有兩種刪除元素的方法

一個是通過角標:public E remove(int index)。其思路是找到指定位置,通過 Systemarraycopy 方法將下標 index 後的元素復制到原數組中直接覆蓋下標為 index 的元素,以此達到刪除的效果。

 public E remove(int index) {
     rangeCheck(index);

     modCount++;
     E oldValue = elementData(index);

     int numMoved = size - index - 1;
     if (numMoved > 0)
         System.arraycopy(elementData, index+1, elementData, index,
                          numMoved);
     elementData[--size] = null; // clear to let GC do its work

     return oldValue;
 }

另一個是直接刪除指定的元素:public boolean remove(Object o)。刪除指定元素,第一步先判斷 o 對象是否為 null,如果是的話,刪除數組中值為 null 的元素,如果不是,則刪除指定的元素。其最終刪除的方法也是通過賦值數組來完成,這裏就不再重復。

3.4 修改元素、查找元素

修改元素與查找元素實現的原理差不多,其中應該註意的是在修改或者查找元素之前,應該先判斷下需要查找的下標是否超出數組的長度,如果是的話,就應該拋出一個異常。ArrayList 是通過 rangeCheck 來判斷的。

public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

ArrayList源碼解析