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)級別的。