【Java源碼】集合類-ArrayList

1、類繼承關係

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList繼承AbstractList,也實現了List和RandomAccess(一個空接口,也就是標記接口。),Cloneable(可被克隆), Serializable接口。java

2、類屬性

//默認容量
    private static final int DEFAULT_CAPACITY = 10;

    //空對象數組(initialCapacity == 0時返回)
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //調用無參構造方法,返回的是該數組
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //元素數組,保存添加到ArrayList中的元素
    transient Object[] elementData;
    
    //ArrayList大小
    private int size;

elementData是transient修飾,因此elementData不能被序列化。可是ArrayList又是能夠被序列化的,這是爲什麼?數組

  • 由於ArrayList有兩個方法writeObject、readObject用於序列化和反序列化。elementData是transient修飾是爲了不ArrayList擴容(1.5倍)致使的空間浪費
  1. 序列化的時候elementData的數據會被寫到ObjectOutputStream中
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();
        }
    }
  1. 反序列化的時候會從ObjectInputStream讀取出來
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
            ensureCapacityInternal(size);

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

3、構造函數

  • 指定容量
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //空對象數組
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
  • 默認
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  • ArrayList(Collection<? extends E> c)
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //空對象數組
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

4、ArrayList如何擴容

public boolean add(E e) {
        //確保elementData數組的大小
        ensureCapacityInternal(size + 1);
        //在結尾處添加元素
        elementData[size++] = e;
        return true;
    }

上面是add(E e)方法的源碼,在添加元素前會調用 ensureCapacityInternal(size + 1);確保elementData數組的大小。dom

private void ensureCapacityInternal(int minCapacity) {
        // 判斷元素數組是否爲空數組
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //minCapacity最少=DEFAULT_CAPACITY=10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
  • 其中ensureCapacityInternal的源碼片斷首先判斷elementData是否爲無參構造器的空數組,若是是的話,Math.max()取較大值賦值給minCapacity,最小是10(默認容量)。而後,調用ensureExplicitCapacity:
private void ensureExplicitCapacity(int minCapacity) {
        // List結構性修改加1
        modCount++;
        //minCapacity大於elementData數組長度
        if (minCapacity - elementData.length > 0)
            //增加
            grow(minCapacity);
    }
  • ensureExplicitCapacity中modCount記錄List結構性變化次數(結構性變化,在迭代的過程當中可能會形成錯誤結果)。
  • 緊接着,若是minCapacity大於elementData數組長度則調用 grow(minCapacity)增加函數
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //oldCapacity >> 1右移一位,這裏至關於擴容1.5倍
        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);
    }

上面的擴容方法中 oldCapacity >> 1 代表elementData.length右移一位,擴容後的容量爲原始容量的1.5倍函數

5、主要函數

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

        modCount++;
        E oldValue = elementData(index);
        //須要移動的元素個數
        int numMoved = size - index - 1;
        //刪除的不是最後一個元素
        if (numMoved > 0)
            //index後的元素都向前移動一位
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        //size減一,釋放引用,方便垃圾回收器進行垃圾回收
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
  • remove(int index)方法會先計算須要移動的元素個數,index後的元素都向前移動一位(調用System.arraycopy方法移動),而後size減一,賦值爲null釋放引用,方便垃圾回收器進行垃圾回收。性能

    單線程環境下,正確的刪除元素,應該使用iterator()迭代器刪除元素。

例如要刪除集合中的「a」this

Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            if ("a".equals(iterator.next())) {
                iterator.remove();
            }
        }

或者使用lambda表達式線程

list.removeIf("x"::equals);
set()函數,會替換新值,返回舊值。
public E set(int index, E element) {
        //檢查索引
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        //返回舊值
        return oldValue;
    }
get()函數,獲取元素
public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

6、ArrayList性能相關

  • 在可以肯定ArrayList容量大小的狀況下儘可能預估容量,調用構造器public ArrayList(int initialCapacity)指定大小,避免由於自動擴容帶來的性能開銷。
  • 使用trimToSize()使ArrayList的內部數組大小減小爲實際大小,以此節省空間
public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }
相關文章
相關標籤/搜索