ArrayList 與 LinkedList性能比較

今天看到有人提問 arrayList 與 LinkedList 性能比較的問題,爲此專門測試了下兩者的插入,刪除,訪問效率
經過測試大概得出以下結論:
ArrayList 與 LinkedList 在順序插入時(末尾插入),數據量較小時(100000以內)LinkedList的插入效率優於 arrayList(但不明顯,最多幾倍的差距),但數據量更大時(40w 以上)此時順序插入 arraylist 的插入性能明顯優於 linkedlist.但如果一直在 list 的位置0插入,linkedList 的插入性能對 arrayList 有指數級性能優勢.
可以查看兩者的插入代碼分析以上現象:
先看 arrayList 的插入代碼,主要代碼如下:

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
 private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    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);
    }
    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++;
    }

分析以上代碼可以看到,arrayList 在順序插入時,如果數據容量不夠,會經常擴容,其中擴容代碼Arrays.copyOf(elementData, newCapacity); 會消耗大量時間,但如果數據量較大,此時擴容次數明顯下降(擴容總是會在當前容量的2倍),因此擴容消耗的時間平均下來明顯降低,但對於每次都插入指定位置0的時候System.arraycopy(elementData, index, elementData, index + 1,size - index); 每次都會執行這個代碼,數組的拷貝會浪費大量時間,因此插入時間會呈指數級增長

對於 linkedList,插入的主要代碼邏輯如下:

public boolean add(E e) {
        linkLast(e);
        return true;
    }
   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++;
    }
   public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

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

可見對於末尾插入,linkedlist 每次打操作都是直接在鏈表末尾插入數據,對於位置0的插入,linkedlist 的操作與在末尾插入基本一致,(linkedlist 對於制定位置操作會首先檢查靠近末尾還是靠近頭元素,會從靠近的一端進行遍歷),因此 linkedList 對於中間部分的插入操作的耗時會基本花在查找元素上

對於插入操作,兩者可以得出以下結論:
1.對於少量元素,儘量使用 arrayList
2.對於大量元素(10w 以上),沒有頻繁的隨機讀取操作,且有大量的 list 前部的插入操作(前10%),此時可以選用 Linkedlist
3.儘量使用 arrayList,除非使用 LinkedList 時的速度是 arrayList 的10倍以上

對於刪除操作:
對於大量數據,如果一直從 list 開始位置開始刪除,linkedlist 對 arrayList 有指數級優勢,但如果從末尾位置開始刪除,arrayList 對 linkedList有較大優勢
arraylist刪除代碼如下

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

主要耗時代碼爲:System.arraycopy(elementData, index+1, elementData, index, numMoved);,每一次刪除 arrayList 都需要將被刪除元素之後的所有元素複製一遍
對於 linkedList 的刪除代碼:

 public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

  E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

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

對於 linkedList 來說刪除的主要耗時還是花在查找元素上
因此對於刪除操作這兩者的建議如下:
對於少量數據的刪除建議使用 arrayList
對於大量數據,總是刪除靠前數據時,建議使用 linkedList
其餘情況使用 arrayList

綜合以上插入與刪除,對於這兩種 list 的綜合建議:
99%的情況用ArrayList,還有1%的情況是:
1、超大List,超大指至少十幾萬個元素,並且還需要頻繁添加刪除(在 list 開始部分),但不需要頻繁訪問
2、當你發現ArrayList比LinkedList耗時多一個數量級的時候

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章