Java ArrayList源碼學習

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

一 自動擴容機制

Resizable-array implementation of the List interface.
capacity: 存儲list元素的底層數組的大小;
list size:存儲的list元素的個數;

  1. 初始化
    不指明容量時,底層數組默認是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,即{}
    指明容量為0時,底層數組為EMPTY_ELEMENTDATA,即{}
    指明容量>0時, this.elementData = new Object[initialCapacity];

    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);
           }
       }
     // Constructs an empty list with an initial capacity of ten.
    public ArrayList() { 
         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
  2. add

        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // 確保底層數組的容量
            elementData[size++] = e;
            return true;
        }
    

    先計算需要的容量,然后進行分配;

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

    計算所需的最小容量
    如果開始沒有指定容量大小,最低容量設為Math.max(10, minCapacity);
    為什么沒有指定容量時,不直接設為10呢?反序列化時也會調用,minCapacity可能>10;
    如果開始制定了容量為0,最低容量是0;

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
    }
    

    底層數組容量是否改變,均執行modCount++; 表示list結構性變化;

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

    實際的數組擴增函數:1.5倍擴增

        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
                                       //實際上小于MAX_ARRAY_SIZE,就報錯OutOfMemoryError
        /**
         * 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);
        }
    

    ArrayList<Integer> list =new ArrayList<Integer>();
    list.add(1); 默認數組分配容量為10

  3. remove
    remove函數并不會調節底層數組的大小;

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

    clear函數也并不會調節底層數組的大小;

        /**
         * Removes all of the elements from this list.  The list will
         * be empty after this call returns.
         */
        public void clear() {
            modCount++;
    
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;
    
            size = 0;
        }
    

    真正減少底層數組容量的函數調用 trimToSize()

        /**
         * Trims the capacity of this <tt>ArrayList</tt> instance to be the
         * list's current size.  An application can use this operation to minimize
         * the storage of an <tt>ArrayList</tt> instance.
         */
        public void trimToSize() {
            modCount++;
            if (size < elementData.length) {
                elementData = (size == 0)
                  ? EMPTY_ELEMENTDATA
                  : Arrays.copyOf(elementData, size);
            }
        }
    

二: 線程不安全

This class is roughly equivalent to Vector, except that it is unsynchronized.
推薦方式:List list = Collections.synchronizedList(new ArrayList(...));


三: Fail-Fast 機制,ConcurrentModificationException

The iterators returned by this class's iterator methods are fail-fast:
if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own ListIterator.remove() methods, the iterator will throw a ConcurrentModificationException

  1. A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification. (內部int值modCount負責記錄更改次數)

  2. 一旦創建了Iterator后,Iterator會記錄當前list的modCount,并保存為expectedModCount;
    此后外部list發生結構性更改,外部modCount更改,而內部expectedModCount不變,再次調用Iterator函數時無法通過檢查,報錯;

    private class Itr implements Iterator<E> {
            int cursor;       // index of next element to return
            int lastRet = -1; // index of last element returned; -1 if no such
            int expectedModCount = modCount;
    
            public boolean hasNext() {
                return cursor != size;
            }
    
            @SuppressWarnings("unchecked")
            public E next() {
                checkForComodification();
                int i = cursor;
                if (i >= size)
                    throw new NoSuchElementException();
                Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length)
                    throw new ConcurrentModificationException();
                cursor = i + 1;
                return (E) elementData[lastRet = i];
            }
    
            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }
    

    Iterator.remove是刪除前一次返回的值;
    Iterator不能連續remove的原因,remove后 lastRet = -1;

  3. 解決方式:
    3.1)Iterator創建之后,不能用ArrayList增刪元素。 只能用Iterator刪除元素;
    因為Iterator.remove會同步更新list的modCount以及expectedModCount;
    subList 和Iterator 一樣,創建后list就不能結構性修改了;list的增刪不要Iterator的操作混在一起
    3.2)java1.8后,貌似removeIf函數式編程也可以避免,待研究;
    注意
    ArrayList線程不安全,改為Vector或者Collections.synchronizedList(list);并不能避免ConcurrentModificationException。
    因為線程安全是多線程排隊訪問list,不同時訪問。仍然允許一個創建了Iterator,另一個修改,只要不是同時就沒問題。


四: 總結

  1. ArrayList默認擴增方式為1.5倍;
  2. ArrayList隨機訪問get,set是0(1)時間開銷,add有可能擴容,移動數組元素0(n)時間開銷;刪除移動數組元素0(n)時間開銷;Iterator.remove移動數組元素0(n)時間開銷;
    數組優勢,數組劣勢
  3. list轉array
      List<String> v = new ArrayList<>();
      return v.toArray(new String[v.size()]);
    

@夢工廠 2018.3.6

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容