原始碼分析–ArrayList(JDK1.8)

陽光、大地和詩歌發表於2019-01-17

  ArrayList是開發常用的有序集合,底層為動態陣列實現。可以插入null,並允許重複。

  下面是原始碼中一些比較重要屬性:

  1、ArrayList預設大小10。

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

  

  2、elementData就是真正存放資料的陣列。elementData[]本身是動態的,並不是陣列的全部空間都會使用,所以加上transient關鍵詞進行修飾,防止自動序列化。

transient Object[] elementData; // non-private to simplify nested class access

  

  3、ArrayList的實際大小。每次進行add或者remove後,都會進行跟蹤修訂。

/**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

 

  下面分析主要方法:

  1、add()方法有兩個實現,一種是直接新增,一種是指定index新增。

  直接新增程式碼如下:

    /**
     * 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;
    }
  • 擴容判斷
  • 元素插入elementData[]尾部,size加1

  

  指定index新增方式:

   public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
  • 根據當前size,判斷指定索引是否合理
  • 擴容判斷
  • 將原陣列中從index往後的全部元素copy到index+1之後的位置。也就是把後續元素的索引全部+1
  • 需插入的元素放入指定index
  • size加1

 

  add()方法中,陣列擴容呼叫的最終方法如下:

   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會在原先容量的基礎上,擴容為原來的1.5倍(oldCapacity + (oldCapacity >> 1)),最大容量為Integer.MAX_VALUE。elementData也就是一個陣列複製的過程了。所以在平常的開發中,例項化ArrayList時,可以儘量指定容量大小,減少擴容帶來的陣列複製開銷。

 

  2、remove()方法和add()類似,這裡就只簡單看下:

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

  也就是拿到刪除元素的index後,用陣列複製的方式進行元素的覆蓋。最後一個elementData陣列的元素就是成了垃圾資料,讓GC進行回收。size減1。

  

  3、序列化

  /**
     * 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();
        }
    }

    /**
     * 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();
            }
        }
    }

  ArrayList是用動態陣列elementData進行資料儲存的,所以本身自定義了序列化與反序列化方法。當物件中自定義了 writeObject 和 readObject 方法時,JVM 會呼叫這兩個自定義方法來實現序列化與反序列化。ArrayList只序列化elementData裡面的資料。

相關文章