ArrayList/Vector 的底層分析

ArrayList

ArrayList 實現於 ListRandomAccess 接口。可以插入空數據,也支持隨機訪問。

ArrayList相當於動態數據,其中最重要的兩個屬性分別是: elementData 數組,以及 size 大小。 在調用 add() 方法的時候:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
  • 首先進行擴容校驗。
  • 將插入的值放到尾部,並將 size + 1 。

如果是調用 add(index,e) 在指定位置添加的話:

    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++;
    }
  • 也是首先擴容校驗。
  • 接着對數據進行復制,目的是把 index 位置空出來放本次插入的數據,並將後面的數據向後移動一個位置。

其實擴容最終調用的代碼:

    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 的主要消耗是數組擴容以及在指定位置添加數據,在日常使用時最好是指定大小,儘量減少擴容。更要減少在指定位置插入數據的操作。

序列化

由於 ArrayList 是基於動態數組實現的,所以並不是所有的空間都被使用。因此使用了 transient 修飾,可以防止被自動序列化。

transient Object[] elementData;

因此 ArrayList 自定義了序列化與反序列化:

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

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

當對象中自定義了 writeObject 和 readObject 方法時,JVM 會調用這兩個自定義方法來實現序列化與反序列化。

從實現中可以看出 ArrayList 只序列化了被使用的數據。

Vector

Vector 也是實現於 List 接口,底層數據結構和 ArrayList 類似,也是一個動態數組存放數據。不過是在 add() 方法的時候使用 synchronized 進行同步寫數據,但是開銷較大,所以 Vector 是一個同步容器並不是一個併發容器。

以下是 add() 方法:

    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

以及指定位置插入數據:

    public void add(int index, E element) {
        insertElementAt(element, index);
    }
    public synchronized void insertElementAt(E obj, int index) {
        modCount++;
        if (index > elementCount) {
            throw new ArrayIndexOutOfBoundsException(index
                                                     + " > " + elementCount);
        }
        ensureCapacityHelper(elementCount + 1);
        System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
        elementData[index] = obj;
        elementCount++;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章