1. 程式人生 > >面試題之---ArrayList實現原理

面試題之---ArrayList實現原理

單列集合圖

1. ArrayList是一個動態陣列,實現了List<E>, RandomAccess, Cloneable, java.io.Serializable,並允許包括null在內的所有元素。

1.1,實現了RandomAccess介面標識著其支援隨機快速訪問,實際上,我們檢視RandomAccess原始碼可以看到,其實裡面什麼都沒有定義.因為ArrayList底層是陣列,那麼隨機快速訪問是理所當然的,訪問速度O(1).

1.2,現了Cloneable介面,標識著可以它可以被複制.注意,ArrayList裡面的clone()複製其實是淺複製

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 預設長度是10
     */
    private static final int DEFAULT_CAPACITY = 10;

public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // 預設長度是10
     : DEFAULT_CAPACITY;

if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); }}

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

    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // 需要保持的資料大於現有的容量時,開始擴容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    //擴充套件為原來的1.5倍,    oldCapacity>>1表示往右移一個單位,就是除以2的1次方
    int newCapacity = oldCapacity + (oldCapacity >> 1);
        
 // 如果擴為1.5倍還不滿足需求,直接擴為你需要的大小
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    //將原來的長度,拷貝變成擴充套件後的大小
    elementData = Arrays.copyOf(elementData, newCapacity);
}

2. 底層使用陣列實現,預設初始容量為10.當超出後,會自動擴容為原來的1.5倍,即自動擴容機制。

   陣列的擴容是新建一個大容量(原始陣列大小+擴充容量)的陣列,然後將原始陣列資料拷貝到新陣列,然後將新陣列作為擴容之後的陣列。陣列擴容的操作代價很高,我們應該儘量減少這種操作。

3. 該集合是可變長度陣列,陣列擴容時,會將老陣列中的元素重新拷貝一份到新的陣列中,每次陣列容量增長大約是其容量的1.5倍,如果擴容一半不夠,就將目標size作為擴容後的容量.這種操作的代價很高。採用的是 Arrays.copyOf淺複製,

    3.1這裡簡單說一下什麼是淺複製

    淺複製:只複製一個物件,但新物件和老物件同是一個地址值,

    深複製:複製一個物件,新老物件的地址值也變了.

    詳情請看(要了解什麼是淺複製):點選開啟連結https://blog.csdn.net/qq_38859786/article/details/80318977

4. 採用了Fail-Fast機制,面對併發的修改時,迭代器很快就會完全失敗,報異常concurrentModificationException(併發修改一次),而不是冒著在將來某個不確定時間發生任意不確定行為的風險

5. remove方法會讓下標到陣列末尾的元素向前移動一個單位,並把最後一位的值置空,方便GC

6. 陣列擴容代價是很高的,因此在實際使用時,我們應該儘量避免陣列容量的擴張。當我們可預知要儲存的元素的多少時,要在構造ArrayList例項時,就指定其容量,以避免陣列擴容的發生。或者根據實際需求,通過呼叫ensureCapacity方法來手動增加ArrayList例項的容量。

7. ArrayList不是執行緒安全的,只能用在單執行緒環境下,多執行緒環境下可以考慮用Collections.synchronizedList(List l)函式返回一個執行緒安全的ArrayList類,也可以使用concurrent併發包下的CopyOnWriteArrayList類。

eventbus的訂閱方法subscribe()裡面,就採用了執行緒較為安全的CopyOnWriteArrayList集合

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

   8,add(E e)方法作用: 新增指定元素到末尾

    如果要增加的資料量很大,應該使用ensureCapacity()方法,該方法的作用是預先設定Arraylist的大小,這樣可以大大提高初始化速度.

Object obj = new Object();
ArrayList list0 = new ArrayList();
long startTime0 = System.currentTimeMillis();
for(int i=0;i<=N;i++){
    list0.add(obj);
}
long endTime0 = System.currentTimeMillis();
Log.e("date", "111沒有呼叫ensureCapacity()方法所用時間:" + (endTime0 - startTime0) + "ms");

ArrayList list1 = new ArrayList();
long startTime1 = System.currentTimeMillis();

list1.ensureCapacity(N);//預先設定list的大小
for(int i=0;i<=N;i++){
    list1.add(obj);
}
long endTime1 = System.currentTimeMillis();
Log.e("date", "222呼叫ensureCapacity()方法所用時間:" + (endTime1 - startTime1) + "ms");

        9,如果是新增到陣列的指定位置,那麼可能會挪動大量的陣列元素,並且可能會觸發擴容機制;如果是新增到末尾的話,那麼只可能觸發擴容機制.

       10,如果是刪除陣列指定位置的元素,那麼可能會挪動大量的陣列元素;如果是刪除末尾元素的話,那麼代價是最小的. ArrayList裡面的刪除元素,其實是將該元素置為null.

        11,Collection是最頂層的集合,Collection.toArray()在Collection各個子類的原始碼中使用頻繁

        12,Arrays.copyOf(U[] original, int newLength, Class<? extends T[]> newType),就是根據class的型別來決定是new還是反射去構造一個泛型陣列,同時利用native函式,批量賦值元素至新陣列中.

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    //根據class的型別來決定是new還是反射去構造一個泛型陣列
            T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    //利用native函式,批量賦值元素至新陣列中
    System.arraycopy(original, 0, copy, 0,
            Math.min(original.length, newLength));
    return copy;
}

      13, System.arraycopy()複製陣列,也是一個高頻使用的函式.

@FastNative
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

借鑑:

集合各實現類的底層實現原理

CopyOnWriteArrayList詳解

Java中ArrayList和LinkedList區別