List代表一種線性表的數據結構,ArrayList則是一種順序存儲的線性表。ArrayList底層採用數組來保存每個集合元素,LinkedList則是一種鏈式存儲的線性表。其本質上就是一個雙向鏈表,但它不僅實現了List接口,還實現了Deque接口。也就是說LinkedList既可以當成雙向鏈表使用,也可以當成隊列使用,還可以當成棧來使用(Deque代表雙端隊列,即具有隊列的特徵,也具有棧的特徵)。
ArrayList底層採用一個elementData數組來保存所有的集合元素,因此ArrayList在插入元素時需要完成下面兩件事情。
- 保證ArrayList底層封裝的數組長度大於集合元素的個數;
- 將插入位置之後的所有數組元素“整體搬家”,向後移動一“格”。
反過來,當刪除ArrayList集合中指定位置的元素時,程序也要進行”整體搬家”,而且還需將被刪索引處的數組元素賦爲null。下面是ArrayList集合的remove(int index)方法的源代碼。
public E remove(int index) {
//如果index是大於或等於size,拋出異常
RangeCheck(index);
modCount++;
//index索引處的元素
E oldValue = (E) elementData[index];
//計算需要"整體搬家"的元素個數
int numMoved = size - index - 1;
//當numMoved大於0時,開始搬家
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//釋放被刪除的元素
elementData[--size] = null; // Let gc do its work
return oldValue;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
對於ArrayList集合而言,當程序向ArrayList中添加、刪除集合元素時,ArrayList底層都需要對數組進行”整體搬家” 因此性能非常差。
但如果程序調用get(int index)方法來取出ArrayList集合中的元素時,性能和數組幾乎相同–非常快。下面是ArrayList集合get(int index)方法的源代碼。
public E get(int index) {
RangeCheck(index);
//取出index索引處的元素
return (E) elementData[index];
}
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
LinkedList本質上就是一個雙向鏈表
,因此它使用如下內部類來保存每個集合元素。
private static class Entry<E> {
//集合元素
E element;
//保存指向下一個鏈表節點的引用
Entry<E> next;
//保存指向上一個鏈表節點的引用
Entry<E> previous;
//普通構造器
Entry(E element, Entry<E> next, Entry<E> previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
從上面代碼可以看出,一個Entry對象代表雙向鏈表的一個節點,該對象中next變量指向下一個節點,previous則指向上一個節點。
由於LinkedList採用雙向鏈表來保存集合元素,因此它在添加集合元素時候,只要對鏈表進行如圖所示的操作即可添加一個新節點。
下面是LinkedList添加節點的源代碼
//在指定位置插入新節點
public void add(int index, E element) {
//如果index==size,直接在header之前插入新節點
//否則,在index索引處的節點之前插入新節點
addBefore(element, (index==size ? header : entry(index)));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
從上面代碼看出,由於LinkedList本質上就是一個雙向鏈表,因此它可以非常方便地在指定節點之前插入新節點,LinkedList在指定位置添加新節點也是通過這種方式來實現的。
上面add(int index, E element)方法實現中用到了以下兩個方法。
- entry(int idnex): 搜索指定索引處的元素。
- addBefore(E element, Entry ref): 在ref節點之前插入element新節點
entry(int index)實際上就是get(int index)方法的底層實現。對於ArrayList而言,由於它底層採用數組來保存集合元素,因此可以直接根據數組索引取出index位置的元素;但對於LinkedList就比較麻煩了,LinkedList必須一個元素一個元素地搜索,直到找到第index個元素爲止。
下面是entry(int index)方法的源代碼
//獲取指定索引處的節點
private Entry<E> entry(int index) {
//如果index越界,拋出異常
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+size);
//從鏈表的頭開始搜索
Entry<E> e = header;
//如果index小於size/2
if (index < (size >> 1)) {
//從鏈表的頭端開始搜索
for (int i = 0; i <= index; i++)
e = e.next;
}
//如果index大於size/2
else {
//從鏈表的尾端開始搜索
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
上面entry(int index)方法就是一個元素一個元素的找到index索引處的元素,只是由於LinkedList是一個雙向鏈表,因此程序先根據index的值判斷它到底離鏈表頭端近(當index
public E get(int index) {
return entry(index).element;
}
- 1
- 2
- 3
- 1
- 2
- 3
無論如何,LinkedList爲了獲取指定索引處的元素都是比較麻煩的,系統開銷也會比較大。但單純的插入操作就比較簡單了,只要修改幾個節點裏的previous、next引用的值。下面是addBefore(E element,Entry ref)方法的源代碼。
//值指定節點(entry)之前添加一個新節點
private Entry<E> addBefore(E e, Entry<E> entry) {
//創建新節點,新節點的下一個節點指向entry,上一個節點指向entry的上一個節點
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
//讓entry的上一個節點向後指向新節點
newEntry.previous.next = newEntry;
//讓entry向前指向新節點
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
如果只是單純地添加某個節點,LinkedList的性能會非常好,可惜如果需要向指定索引處添加節點,LinkedList必須先找到指定索引處的節點 – 這個搜索過程的系統開銷並不小,因此LinkedList的add(int index,E element)方法的性能並不是特別好。如果希望從LinkedList中刪除一個節點,底層雙向鏈表可按如圖所示操作。
類似地,LinkedList爲了實現remove(int index)方法 – 刪除指定索引處的節點,也必須先通過entry(int index)方法找到index索引處的節點,然後修改它前一個節點的next引用以及後一個節點的previous引用。下面是LinkedList的remove(int index)方法的源代碼
public E remove(int index) {
//搜索到index索引處的節點,然後刪除該節點
return remove(entry(index));
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
從上面代碼可以看出,程序先調用entry(index)搜索到index索引處的節點,然後調用remove(Entry entry)方法刪除指定節點。刪除entry節點時只需修改entry前一個節點next引用,修改entry後一個節點的previous引用。下面是該方法的源代碼。
private E remove(Entry<E> e) {
if (e == header)
throw new NoSuchElementException();
//先保存e節點的元素
E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
//將被刪除的元素的兩個引用、元素都賦爲null、以便垃圾回收
e.next = e.previous = null;
e.element = null;
size--;
modCount++;
return result;
}
轉載出處: http://blog.csdn.net/itmyhome1990/article/details/76135200