集合的作用就是以一定的方式組織,存儲數據。對於LinkedList,我認爲需要的關注以下內容:
1.集合的基本存儲單元
2.集合的增刪改查基本操作的實現
3.存儲數據的要求,是否爲空,是否允許重複
4.存放與讀取是否有序
5.是否線程安全
基本存儲單元
LinkedList的源碼片段:
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
雙向鏈表實現,鏈表長度size,頭指針first,尾指針last。
集合的增刪改查基本操作的實現
1.新增元素
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++;
}
新建一個l節點指向當前last節點,新建一個newNode節點,將newNode的prev指向l地址,last節點指向newNode,如果鏈表中無數據,將first節點指向newNode,如果存在數據,將l節點指向newNode節點,這樣就實現了l與newNode的雙向鏈接。
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++;
}
插入元素時,當插入的是最後一個元素時(即順序插入),和add方法一致,這裏分析下中間插入。新建一個pred節點指向當前succ節點的上一個位置,新建newNode節點,newNode的prev指向pred,next指向succ,此時newNode節點已經鏈接了前後節點,接下來需要將前節點的next指向newNode,及將後節點的prev指向newNode,這樣,newNode的雙向鏈接就完成了。
2.修改元素
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
用node方法取出節點,修改val,後面分析node方法。
3.刪除元素
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;
}
先找出當前節點x的前後節點,prev節點及next節點。將prev的next指向next節點,將next的prev指向prev節點,最後將x的prev和next設置爲null。
4.尋址
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;
}
}
尋址方式比較重要,這裏用了位運算,加快速度。當index小於長度的一般,從頭開始尋址;反之從尾部開始尋址。
其實鏈表操作的分析最好在紙上畫出來,先找出當前節點的前後節點,更改鏈接的方向,一目瞭然。
存儲數據的要求,是否爲空,是否允許重複
允許爲空或者null,允許重複
存放與讀取是否有序
底層實現是雙向鏈表,所以有序
是否線程安全
非線程安全,因爲所有方法都是不同步的。
LinkedList與ArrayList的比較
LinkdList基於Node數組的實現,每次插入都需new一個Node對象,並維護一些引用地址,所以耗費的空間和時間比ArrayList要大。但是當集合的數據量變大,而且需要中間插入數據的操作變多,ArrayList就需要耗費大量時間和空間去進行數據copy,大大降低了性能,這時LinkedList的優勢就顯現出來了。總體來說,LinkedList快在維持引用關係,慢在尋址,但ArrayList快在尋址,慢在數組的copy。
再說一下迭代的問題,ArrayList實現了RandomAccess接口,支持快速隨機訪問,ArrayList在隨機或連續訪問列表是都有良好的性能,for和foreach的速度都很快。LinkedList在用foreach,即Iteratior迭代器訪問時有良好的性能表現,但在普通for循環時,速度會慢的讓人發狂。
以下爲測試結果,單位ms:
init arrayList : 62
init linkedList : 47
init finished!
test005 ArrayList 普通for循環速度(10W條數據): 1
test006 ArrayList Iterator循環速度(10W條數據): 16
test007 LinkedList 普通for循環速度(10W條數據): 37781
test008 : LinkedList Iterator循環速度(10W條數據)16