ArrayList/LinkedList 的底層分析

ArrayList

ArrayList 實現於 List、RandomAccess 接口。能夠插入空數據,也支持隨機訪問。java

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

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 位置空出來放本次插入的數據,並將後面的數據向後移動一個位置。

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

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

 

LinkedList 底層分析

如圖所示 LinkedList 底層是基於雙向鏈表實現的,也是實現了 List 接口,因此也擁有 List 的一些特色(JDK1.7/8 以後取消了循環,修改成雙向鏈表)。spa

新增方法

public boolean add(E e) {
        linkLast(e);
        return true;
    }
     /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

可見每次插入都是移動指針,和 ArrayList 的拷貝數組來講效率要高上很多。指針

查詢方法

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

上述代碼,利用了雙向鏈表的特性,若是index離鏈表頭比較近,就從節點頭部遍歷。不然就從節點尾部開始遍歷。使用空間(雙向鏈表)來換取時間。code

  • node()會以O(n/2)的性能去獲取一個結點
    • 若是索引值大於鏈表大小的一半,那麼將從尾結點開始遍歷

這樣的效率是很是低的,特別是當 index 越接近 size 的中間值時。blog

總結:

  • LinkedList 插入,刪除都是移動指針效率很高。
  • 查找須要進行遍歷查詢,效率較低。
  • 特例:再插入數據時,若是事先設定好數組的長度,避免數組擴容的話,其實效率比linkedlist的插入效率還要快,不信的小夥伴能夠去嘗試一下。
相關文章
相關標籤/搜索