ArrayList 源碼分析

世上的事,只要肯用心去學,沒有一件是太晚的。請你必定不要停下來,成爲你想成爲的人。html

前言

learn from collection framework design中提到,collection framework分爲兩部分,分別爲CollectionMap,其中Collection又分爲三類分別爲ListSetQueue,本篇文章先來分析ArrayList的實現。java

ArrayList繼承關係


如上圖所示,它實現了RandomAccess(可隨機訪問),Cloneable(可克隆),Serializable(支持序列化和反序列化)接口以及List接口,而且它還繼承了List的抽象模板類AbstractList
其中,前三個接口都是marker interface,沒有可讓實現類實現的方法。算法

下面直接來看ArrayList內部的一些實現機制。數組

內部實現

數據結構

其內部維護了一個Object類型的數組,即elementData成員變量,成員變量size記錄list的大小。。安全

初始化

ArrayList的構造方法有以下三種重載,分別是:
第一種方式:根據初始容量初始化ArrayList。數據結構

/**
 * Constructs an empty list with the specified initial capacity.
 *
 * @param  initialCapacity  the initial capacity of the list
 * @throws IllegalArgumentException if the specified initial capacity
 *         is negative
 */
public ArrayList(int initialCapacity) {
	if (initialCapacity > 0) { // 根據傳入的初始的容量大小初始化List,其內部維護的是
		this.elementData = new Object[initialCapacity];
	} else if (initialCapacity == 0) {
		this.elementData = EMPTY_ELEMENTDATA; // 是一個長度爲0的空數組,即{}
	} else { // 因數組長度不能小於0,故拋出異常
		throw new IllegalArgumentException("Illegal Capacity: "+
										   initialCapacity);
	}
}

第二種:使用默認大小,默認內部數組長度爲0。app

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // DEFAULTCAPACITY_EMPTY_ELEMENTDATA默認爲長度爲0的空數組
}

第三種:根據傳入的集合構建ArrayListdom

/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 *
 * @param c the collection whose elements are to be placed into this list
 * @throws NullPointerException if the specified collection is null
 */
public ArrayList(Collection<? extends E> c) {
	elementData = c.toArray(); // 注意,先構造一個新的數組,而後使用數組拷貝,將舊數據拷貝到新數組,這樣效率並不高,而且還浪費內存
	if ((size = elementData.length) != 0) { // collection包含元素
		// c.toArray might (incorrectly) not return Object[] (see 6260652)
		if (elementData.getClass() != Object[].class)
			elementData = Arrays.copyOf(elementData, size, Object[].class);
	} else { // collection不包含元素,使用內部預約義的長度爲0的數組。
		// replace with empty array.
		this.elementData = EMPTY_ELEMENTDATA;
	}
}

內部數組擴容機制

java.util.ArrayList#ensureCapacityInternal是專門用於擴容的私有方法,具體以下:ide

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

一共有兩個步驟,分別爲計算所需容量以及擴容兩個步。測試

計算所需容量

calculateCapacity源碼以下:

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity); // 若是剛開始是空數組,則第一次擴容,數組長度需擴容到 max(10,須要的最小容量)
    }
    return minCapacity;
}

擴容

private void ensureExplicitCapacity(int minCapacity) {
    modCount++; // 記錄內部數組擴容次數

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

這裏爲何要用減法而不直接比較?
由於minCapacity這個是由原始的大小 + 須要插入的元素的個數獲得的,在加法運算後可能會出現溢出,變爲負數,變爲負數了就不能繼續擴容了。
grow具體以下:

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
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);
}

huge源碼以下:

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

擴容倍數是1.5,最大數組長度爲 MAX_ARRAY_SIZE,即Integer.MAX_VALUE - 8,之因此要取這個值是由於,有的JVM在實現數組的時候,剛開始會保留一些header的信息,這些信息會佔8個字節。在擴展數組時,長度一旦超過這個大小,會拋出OutOfMemoryError異常。
也就是說,若是當前數組不足以容納新的元素,則須要1.5倍擴容,最終容量最大爲Integer.MAX_VALUE - 8

單個元素插入

有兩種方式,分別以下:
方式一,默認在結尾插入,以下:

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

方式二,在指定位置插入元素,以下:

/**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 *
 * @param index index at which the specified element is to be inserted
 * @param element element to be inserted
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
  rangeCheckForAdd(index); // 注意,檢查下標的合法性,這個下標是跟ArrayList的長度比較的,不是跟內部數據的capacity比較的!

  ensureCapacityInternal(size + 1);  // Increments modCount!!
  // 把指定下標後(包括該下標)的數據總體後移一位
  System.arraycopy(elementData, index, elementData, index + 1,
                   size - index);
  elementData[index] = element;
  size++;
}

多個元素插入

也有兩種方式。
方式一,在結尾插入,以下:

/**
 * Appends all of the elements in the specified collection to the end of
 * this list, in the order that they are returned by the
 * specified collection's Iterator.  The behavior of this operation is
 * undefined if the specified collection is modified while the operation
 * is in progress.  (This implies that the behavior of this call is
 * undefined if the specified collection is this list, and this
 * list is nonempty.)
 *
 * @param c collection containing elements to be added to this list
 * @return <tt>true</tt> if this list changed as a result of the call
 * @throws NullPointerException if the specified collection is null
 */
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

方式二,在指定位置插入,以下:

/**
 * Inserts all of the elements in the specified collection into this
 * list, starting at the specified position.  Shifts the element
 * currently at that position (if any) and any subsequent elements to
 * the right (increases their indices).  The new elements will appear
 * in the list in the order that they are returned by the
 * specified collection's iterator.
 *
 * @param index index at which to insert the first element from the
 *              specified collection
 * @param c collection containing elements to be added to this list
 * @return <tt>true</tt> if this list changed as a result of the call
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @throws NullPointerException if the specified collection is null
 */
public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount

    int numMoved = size - index; // 計算須要index後(包括index)空出的元素的個數
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);

    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

移除單個元素

主要有兩種方式,分別爲:
方式一,移出指定下標對應位置的元素,以下:

/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).
 *
 * @param index the index of the element to be removed
 * @return the element that was removed from the list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
    rangeCheck(index); // index 有效性校驗,跟 內部元素個數 size 比較

    modCount++;
    E oldValue = elementData(index); // 獲取指定下標下的元素

    int numMoved = size - index - 1; // 計算須要移動的元素的個數
    if (numMoved > 0) // 指定index後的全部元素統一貫前一個索引距離
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work 設置爲null,容許gc回收不用的對象,並更新list的大小

    return oldValue;
}

方式二,移出左邊第一個出現的指定元素

/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present.  If the list does not contain the element, it is
 * unchanged.  More formally, removes the element with the lowest index
 * <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
 * (if such an element exists).  Returns <tt>true</tt> if this list
 * contained the specified element (or equivalently, if this list
 * changed as a result of the call).
 *
 * @param o element to be removed from this list, if present
 * @return <tt>true</tt> if this list contained the specified element
 */
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

注意,其一,判斷相等使用的是equals方法,自定義的對象,須要根據本身的需求從新實現其equals方法;其二,從左向右遍歷,只移出第一個跟指定對象相等(equals)的對象。

其中,fastRemove方法以下:

/*
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 */
private void fastRemove(int index) {
    modCount++; // 修改次數+1
    int numMoved = size - index - 1; // 計算須要向前移動的元素的個數
    if (numMoved > 0) // 若是須要移動,則將index後的元素統一貫前移動一個元素大小位置,並把最後的元素的引用設爲null,便於gc回收再也不使用的對象,並更新list的大小。
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

移除多個元素

方式一,移除全部元素

/**
 * Removes all of the elements from this list.  The list will
 * be empty after this call returns.
 */
public void clear() {
    modCount++; // 修改次數 + 1

    // clear to let GC do its work
    for (int i = 0; i < size; i++) // 全部索引下標下的元素引用設置爲null
        elementData[i] = null;

    size = 0; // 重置list的大小爲0
}

方式二,移出指定範圍內的元素,包括開始索引不包括結束索引

/**
 * Removes from this list all of the elements whose index is between
 * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
 * Shifts any succeeding elements to the left (reduces their index).
 * This call shortens the list by {@code (toIndex - fromIndex)} elements.
 * (If {@code toIndex==fromIndex}, this operation has no effect.)
 *
 * @throws IndexOutOfBoundsException if {@code fromIndex} or
 *         {@code toIndex} is out of range
 *         ({@code fromIndex < 0 ||
 *          fromIndex >= size() ||
 *          toIndex > size() ||
 *          toIndex < fromIndex})
 */
protected void removeRange(int fromIndex, int toIndex) {
    modCount++; // 修改次數 + 1
    int numMoved = size - toIndex; // 計算須要移動的元素的個數
    System.arraycopy(elementData, toIndex, elementData, fromIndex,
                     numMoved);

    // clear to let GC do its work
    int newSize = size - (toIndex-fromIndex); // 計算list新的大小
    for (int i = newSize; i < size; i++) { // 從後往前依次清除指定位置上的元素
        elementData[i] = null;
    }
    size = newSize; // 更新list的大小
}

注意,這種方式是一個protected類型的,即只容許ArrayList子類或其自己調用的方法。

方式三,批量移出給定集合內的元素或不在給定集合內的元素

private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++) // 從前向後遍歷
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        if (r != size) { // 剩餘的總體前移
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) { // 有元素被移除
            // clear to let GC do its work
            for (int i = w; i < size; i++) // 移除以後的設置爲null
                elementData[i] = null;
            modCount += size - w; // 修改次數 + 移除的元素的個數
            size = w; // 修改list的大小
            modified = true; // 設置修改標誌位爲true
        }
    }
    return modified;
}

數據移除採用的是雙指針,指針w維護的是新的list,指針r用於遍歷舊的list,一次外層循環遍歷便可獲得新的list,其中w是新的list的大小,算法複雜度是O(n)

方式四,移除指定集合內的全部元素

public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}

其內部調用的是方式三的方法,不作過多說明。
方式五,移除指定集合外的全部元素

public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}

方式六,移除符合條件的全部數據

@Override
public boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    // figure out which elements are to be removed
    // any exception thrown from the filter predicate at this stage
    // will leave the collection unmodified
    int removeCount = 0;
    final BitSet removeSet = new BitSet(size);
    final int expectedModCount = modCount;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        @SuppressWarnings("unchecked")
        final E element = (E) elementData[i];
        if (filter.test(element)) {
            removeSet.set(i);
            removeCount++;
        }
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }

    // shift surviving elements left over the spaces left by removed elements
    final boolean anyToRemove = removeCount > 0;
    if (anyToRemove) {
        final int newSize = size - removeCount;
        for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
            i = removeSet.nextClearBit(i);
            elementData[j] = elementData[i];
        }
        for (int k=newSize; k < size; k++) {
            elementData[k] = null;  // Let gc do its work
        }
        this.size = newSize;
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

    return anyToRemove;
}

對序列化的支持

/**
 * Save the state of the <tt>ArrayList</tt> instance to a stream (that
 * is, serialize it).
 *
 * @serialData The length of the array backing the <tt>ArrayList</tt>
 *             instance is emitted (int), followed by all of its elements
 *             (each an <tt>Object</tt>) in the proper order.
 */
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

注意,在序列化的時候,list大小不能修改,序列化的時候把list的大小size也保存下來了。

/**
 * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
 * deserialize it).
 */
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt(); // ignored

    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}

反序列化後,list的capacity和size是同樣的。
測試代碼以下:

package com.company;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) throws Exception {
	// write your code here
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 12; i++) {
            list.add(i);
        }
        System.out.println(list.size());
        System.out.println(list);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(list);
        oos.flush();
        byte[] bytes = os.toByteArray();
        ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
        List<Integer> o = (List<Integer>)inputStream.readObject();
        System.out.println(o.size());
        System.out.println(o);
        Field elementData1 = o.getClass().getDeclaredField("elementData");
        elementData1.setAccessible(true);
        Object[] elementData = (Object[]) elementData1.get(list);
        System.out.println(elementData.length);
        elementData = (Object[]) elementData1.get(o);
        System.out.println(elementData.length);
    }
}

替換

替換,本質上就是一個變換,只不過這個是在原數據上修改。

@Override
@SuppressWarnings("unchecked")
public void replaceAll(UnaryOperator<E> operator) {
    Objects.requireNonNull(operator);
    final int expectedModCount = modCount;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        elementData[i] = operator.apply((E) elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

排序

排序,其實現了通用的排序算法(調用Array.sort方法),排序比較規則交給用戶來指定。

@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

遍歷

  • Itr實現了能夠向後遍歷remove操做的迭代器,由iterator方法返回。
  • ListItr實現了能夠向前遍歷向後遍歷元素的添加刪除修改的迭代器,由listIterator方法返回。

關於遍歷,不得不說一個很是有名的異常 - ConcurrentModificationException, 多數狀況下是因爲list內部數組長度發生變化致使,modCount != expectedModCount或者是IndexOutOfBoundsException等等緣由拋出的這個異常,遵循一個原則,在使用迭代器的時候,不能直接調用list的方法來修改list而要經過迭代器提供的響應方法來修改list。

ArrayList的優點和缺點

優點

  • 順序存儲,隨機存取,數據元素與位置相關聯,所以查找效率高,索引遍歷快,時間複雜度O(1)
  • 尾部插入與刪除的速度速度快

缺點

  • 線程不安全
  • 非尾節點的插入和刪除須要移除後續的元素,效率較低
  • 支持擴容不支持縮容,擴容後,原數據需逐一拷貝,效率較低

總結

本篇文章,相對來講比較簡單,歸根結底,對ArrayList的各類操做都是對底層數組的操做,深入理解數組這種很是簡單的數據結構對理解ArrayList的各個操做有很大幫助。

相關文章
相關標籤/搜索