1. 程式人生 > >ArrayList 與 LinkedList的插入效率實踐分析

ArrayList 與 LinkedList的插入效率實踐分析

ArrayList 與 LinkedList的效率實踐分析

我們已知的ArrayList以及LinkedList是如下的一個描述:

ArrayList 底層使用連續空間進行順序儲存,隨機查詢快O(1),增加和刪除慢

LinkedList 底層使用雙向佇列實現,隨機查詢較慢,插入速度,刪除速度快

但是不經過驗證如何說明問題。本文將會對ArrayList和LinkedList的插入、查詢、刪除進行實驗,通過實驗得到的資料來說明效能問題

測試機器配置:
CPU: i7 - 6700HQ
記憶體:16GB

1.ArrayList與LinkedList的極端插入情況

何為極端插入?,我們令一個列表是S[1…n],我們知道ArrayList是順序儲存,每一次插入都需要移動元素,如果我們將資料插入S[1],那麼陣列將會最大次數的移動元素,會導致記憶體資料的大批量複製這也是插入的最壞情況。在ArrayList原始碼中的add(int index,E element)是這樣做的

  /**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 *
 * @param index index at which the specified element is to be inserted
 * @param element element to be inserted
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
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++;
}

而LinkedList採用連結串列,則不存在移動元素的問題,只是會新建節點並修改相關引用。

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

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

/**
 * Inserts element e before non-null Node succ.
 */
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++;
}
/**
 * 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++;
}
我們具體的測試程式碼如下:
    List<Integer> list = new ArrayList<>();
    Integer n = new Integer(1);
    long start = System.currentTimeMillis();
    for (int i = 0; i < 50000; i++) {
        list.add(0, n);
    }
    long end = System.currentTimeMillis();
    System.out.println("arraylist time:" + (end - start));


    List<Integer> list1 = new LinkedList<>();
    start = System.currentTimeMillis();
    for (int i = 0; i < 50000; i++) {
        list1.add(0, n);
    }
    end = System.currentTimeMillis();
    System.out.println("linkedlist time:" + (end - start));


得出的結果是
arraylist time:128
linkedlist time:4

2.ArryList與LinkedList的隨機插入情況

隨機插入我們採用生成隨機數的做法來模擬.

    List<String> list = new ArrayList<>();
    Random random=new Random();
    long start = System.currentTimeMillis();
    for (int i = 1; i < 50000; i++) {
        int x=random.nextInt(i);
        list.add(x,"a");
    }
    long end = System.currentTimeMillis();
    System.out.println("arrayList insert time "+(end-start));


    List<String> list1 = new LinkedList<>();
    start = System.currentTimeMillis();
    for (int i = 1; i < 50000; i++) {
        int x=random.nextInt(i);
        list1.add(x,"a");
    }
    end = System.currentTimeMillis();
    System.out.println("arrayList insert time "+(end-start));
   結果是:
   arrayList insert time 68
   linkedList insert time 3000

3.ArrayList與LinkedList的尾部插入情況

ArrayList<String> arr = new ArrayList<>();
long start =System.currentTimeMillis();
for (int i = 0; i < 200000; i++) {
    arr.add("a");
}
long end=System.currentTimeMillis();
System.out.println("arrylist time:" + (end - start));


LinkedList<String> link = new LinkedList<>();
start =System.currentTimeMillis();
for (int i = 0; i < 200000; i++) {
    link.add( "a");
}
end=System.currentTimeMillis();
System.out.println("linkedlist time:" + (end - start));

結果是:
arrylist time:9
linkedlist time:5

但這個僅僅是20w條資料的樣子,如果我們資料再大一點會怎麼樣呢?這次我們調節資料到100w,200w條?
100w條時:
arrylist time:15
linkedlist time:11
200w條時:
arrylist time:28
linkedlist time:76
劇情開始反轉了,具體測算後大約是150w左右運算時間相差無幾,但是超過這個限度就會發生極大變化(可能不同機器不一樣),
之後arraylist的效能反而會比linkedlist好。但是實際上我們用到那麼大的資料量實在是太少了。
為何之後linkedlist會比arraylist插入速度慢的原因我認為可能是由於linkedlist進行記憶體分配的原因,arraylist的記憶體管理採用記憶體擴容的方案,初始是10,擴容時每次增加老容量的一半,然後直接插入資料


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

而linkedlist是每一次都需要去new新物件,修改與連結串列之間的相互引用。一次性分配記憶體總是會比多次分配記憶體花費的時間少。但是還是不足以解釋這個問題,如果對這個問題有答案的朋友可以說下。

關於ArrayList與LinkedList的刪除和查詢便不再敘述了,LibkedList是基於列表實現的,刪除過程中只需修改引用,而ArrayList還是需要進行記憶體複製。關於查詢,ArrayList是基於順序儲存的,可以通過索引訪問,訪問速度O(1),但是LibkedList首先需要從頭節點進行順序查,時間複雜度是O(n)級別的。