什麼情況下ArrayList增刪 比LinkedList 更快
public static void main(String[] args){ final int MAX_VAL = 10000; List<Integer> linkedList = new LinkedList<Integer>(); List<Integer> arrayList = new ArrayList<Integer>(); for(int i = 0; i < MAX_VAL; i++) { linkedList.add(i); arrayList.add(i); }long time = System.nanoTime(); for(int i = 0; i < MAX_VAL; i++) { linkedList.add(MAX_VAL/2, i); } System.out.println("LL time: " + (System.nanoTime() - time)); time = System.nanoTime(); for(int i = 0; i < MAX_VAL; i++) { arrayList.add(MAX_VAL/2, i); } System.out.println("AL time: " + (System.nanoTime() - time)); }
從中間插入結果:
怎麼會這樣, 不應該是LinkedList更快嗎? ArrayList底層是陣列, 新增資料需要移動後面的資料, 而LinkedList使用的是連結串列, 直接移動指標就行, 按理說應該是LinkedList更快.
再來看
從尾插入
public static void main(String[] args){ final int MAX_VAL = 10000; List<Integer> linkedList = new LinkedList<Integer>(); List<Integer> arrayList = new ArrayList<Integer>(); for(int i = 0; i < MAX_VAL; i++) { linkedList.add(i); arrayList.add(i); } long time = System.nanoTime(); for(int i = 0; i < MAX_VAL; i++) { linkedList.add(i); } System.out.println("LL time: " + (System.nanoTime() - time)); time = System.nanoTime(); for(int i = 0; i < MAX_VAL; i++) { arrayList.add(i); } System.out.println("AL time: " + (System.nanoTime() - time)); }
從頭開始插入
public static void main(String[] args){ final int MAX_VAL = 10000; List<Integer> linkedList = new LinkedList<Integer>(); List<Integer> arrayList = new ArrayList<Integer>(); for(int i = 0; i < MAX_VAL; i++) { linkedList.add(i); arrayList.add(i); } long time = System.nanoTime(); for(int i = 0; i < MAX_VAL; i++) { linkedList.add(0,i); } System.out.println("LL time: " + (System.nanoTime() - time)); time = System.nanoTime(); for(int i = 0; i < MAX_VAL; i++) { arrayList.add(0,i); } System.out.println("AL time: " + (System.nanoTime() - time)); }
結果
然後從三分之一的位置開始插入
結果
從三分之二的位置插入
結果
原始碼部分
LinkedList原始碼
// 在index前新增節點,且節點的值為element public void add(int index, E element) { addBefore(element, (index==size ? header : entry(index))); } // 獲取雙向連結串列中指定位置的節點 private Entry<E> entry(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size); Entry<E> e = header; // 獲取index處的節點。 // 若index < 雙向連結串列長度的1/2,則從前向後查詢; // 否則,從後向前查詢。 if (index < (size >> 1)) { for (int i = 0; i <= index; i++) e = e.next; } else { for (int i = size; i > index; i--) e = e.previous; } return e; } // 將節點(節點資料是e)新增到entry節點之前。 private Entry<E> addBefore(E e, Entry<E> entry) { // 新建節點newEntry,將newEntry插入到節點e之前;並且設定newEntry的資料是e Entry<E> newEntry = new Entry<E>(e, entry, entry.previous); // 插入newEntry到連結串列中 newEntry.previous.next = newEntry; newEntry.next.previous = newEntry; size++; modCount++; return newEntry;
從中,我們可以看出:通過add(int index, E element)向LinkedList插入元素時。先是在雙向連結串列中找到要插入節點的位置index;找到之後,再插入一個新節點。
雙向連結串列查詢index位置的節點時,有一個加速動作:若index < 雙向連結串列長度的1/2,則從前向後查詢; 否則,從後向前查詢。
接著,我們看看ArrayList.java中向指定位置插入元素的程式碼。如下:
// 將e新增到ArrayList的指定位置 public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); ensureCapacity(size+1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
ensureCapacity(size+1) 的作用是“確認ArrayList的容量,若容量不夠,則增加容量。”
真正耗時的操作是 System.arraycopy(elementData, index, elementData, index + 1, size - index);
實際上,我們只需要瞭解: System.arraycopy(elementData, index, elementData, index + 1, size - index); 會移動index之後所有元素即可。這就意味著,ArrayList的add(int index, E element)函式,會引起index之後所有元素的改變!
結論:
現在大概知道了,插入位置的選取對LinkedList有很大的影響,一直往資料中間部分插入刪除的時候,ArrayList比LinkedList更快
原因大概就是當資料量大的時候,system.arraycopy的效率要比每次插入LinkedList都需要從端查詢index和分配節點node來的更快。
總之,對於99%或更多的現實情況,ArrayList是更好的選擇,並且利用LinkedList的狹隘優勢需要非常小心。