【Java集合】試讀LinkedList源碼
LinkedList的本質是雙向鏈表。
(01) LinkedList繼承於AbstractSequentialList,並且實現了Dequeue接口。
(02) LinkedList包含兩個重要的成員:header 和 size。
header是雙向鏈表的表頭,它是雙向鏈表節點所對應的類Entry的實例。Entry中包含成員變量: previous, next, element。其中,previous是該節點的上一個節點,next是該節點的下一個節點,element是該節點所包含的值。
size是雙向鏈表中節點的個數。
(前面照舊是復制粘貼的圖和文字,大家大概理解一下,下面進入正題)
為了理解上面的概念,首先我們來看一下核心類Node
//節點,有前驅,後繼和值三個字端,其中前驅和後繼也是節點 private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
Node表示的是結點,結點裏面有三個元素:
數據,前驅和後繼。
其中數據可是任意類型,前驅和後繼同樣是結點。
我們可以想象一個雙向鏈表依次一共有A,B,C三個結點,他們的數據分別為a,b,c。那麽:
A的前驅為null,後繼為B,數據為a。
B的前驅為A,後繼為C,數據為b。
C的前驅為B,後繼為null,數據為c。
接下來我們來看一下構造函數和類變量
//集合元素個數 transient int size = 0; //第一個節點 transient Node<E> first; //最後一個節點 transient Node<E> last; //輸入為空的構造函數 public LinkedList() { } //直接傳入一個Collection放入LinkedList中的構造器, publicLinkedList(Collection<? extends E> c) { //調用無參的構造期 this(); addAll(c); }
類變量分別是List中數據的個數,第一個結點和最後一個結點。
構造函數有兩個:一個是空的構造函數,一個是傳入一個Collection來生成LinkedList。
我們來具體看一下這個addAll方法
//將指定集合c中所有的元素,按照其叠代器返回的順序全部追加到集合的結尾。 public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } //將指定集合c中所有的元素,按照其叠代器返回的順序全部追加到集合的特定位置。 public boolean addAll(int index, Collection<? extends E> c) { checkPositionIndex(index); Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) return false; //pred是predecessor前置節點,succ是succeed 後置節點,請大家學好英語(笑) Node<E> pred, succ; if (index == size) { //新增節點在最後一個 succ = null; pred = last; } else { //新增結點在index處 succ = node(index); pred = succ.prev; } //前驅節點不為null的情況下,循環生成新節點,把前任節點作為新節點的前驅,數組裏的數作為節點的值,後繼置為空 //然後把新節點作為前驅的後繼,之後把新節點作為前驅,繼續循環執行 //可能你這個時候會有疑問,那不是沒有制定後繼?並不是的,後繼是在你的新節點變為前驅後,由 pred.next = newNode;這一句指定的。 for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null); if (pred == null) first = newNode; else pred.next = newNode; pred = newNode; } //後繼為空,則最後一個就是前驅(也就是前面最後一句指定為前驅的newNode) //後繼不為空的話,則把後繼作為前驅(就是前面最後一句指定為前驅的newNode)的後繼,前驅作為後繼的前驅 if (succ == null) { last = pred; } else { pred.next = succ; succ.prev = pred; } //列表裏的數增加 size += numNew; //這個用來判斷叠代器的fast-fail的,具體見我的前一篇ArrayList的那篇博文 modCount++; return true; }
整個把Collection變為LinkedList的過程寫的比較詳細了,不再贅述。
現在我們隨便看一些常用的方法,比如說獲取第一個結點的值,我們發現會有getFirst()和peekFirst()這樣兩個方法;同樣的獲取最後一個結點的值,我們發現會有getLast()和peekLast()兩個方法。那麽為何會有兩種呢?
我們看一下源碼:
//獲取第一個結點的值 public E getFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return f.item; } //獲取的第一個結點的值 public E peekFirst() { final Node<E> f = first; return (f == null) ? null : f.item; }
我們可以看出來,前者如果結點為空會報錯,後者如果結點為空則會返回null。
以下對第一個結點和最後一個結點的操作:
第一個結點(頭部) 最後一個結點(尾部)
拋出異常 特殊值 拋出異常 特殊值
插入 addFirst(e) offerFirst(e) addLast(e) offerLast(e)
移除 removeFirst() pollFirst() removeLast() pollLast()
檢查 getFirst() peekFirst() getLast() peekLast()
左邊的操作遇到異常會拋出異常,右邊的操作遇到異常會返回特殊值。
由於LinkedLIst分別實現了隊列和棧的接口,以下也是對第一個結點和最後一個結點的操作
當作為隊列時,下表的方法等價:
隊列方法 等效方法 add(e) addLast(e) offer(e) offerLast(e) remove() removeFirst() poll() pollFirst() element() getFirst() peek() peekFirst()
當作為棧時下表的方法等價:
棧方法 等效方法
push(e) addFirst(e)
pop() removeFirst()
peek() peekFirst()
以上說的都是對第一個結點和最後一個結點的操作,接下來寫一下對中間結點的操作:
//返回特定位置的結點的值 public E get(int index) { checkElementIndex(index); return node(index).item; } //替換特定位置的結點的值,返回舊的值 public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; } //替換特定位置的結點,原結點向後移 public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } //刪除特定位置的結點 public E remove(int index) { checkElementIndex(index); return unlink(node(index)); }
裏面的具體操作如下:
//獲取某個index的結點 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; } } //把輸入的數e作為新增在最前面的結點的值 private void linkFirst(E e) { final Node<E> f = first; final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; } //把輸入的數e作為新增在最後面的結點的值 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++; } //把輸入的數e作為新增在結點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++; } //把非空的LinkedList的第一個節點unlinked(刪除) private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC first = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; } //把非空的LinkedList的最後一個節點unlinked(刪除) private E unlinkLast(Node<E> l) { // assert l == last && l != null; final E element = l.item; final Node<E> prev = l.prev; l.item = null; l.prev = null; // help GC last = prev; if (prev == null) first = null; else prev.next = null; size--; modCount++; return element; } ///把非空的LinkedList的某個節點unlinked(刪除) 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; }
可以從上面看出來新增和刪除元素都是比較方便的。
還有兩個比較特殊的刪除方法:
//刪除第一個出現的特定值 public boolean removeFirstOccurrence(Object o) { return remove(o); } //刪除最後一個出現的特定值 public boolean removeLastOccurrence(Object o) { if (o == null) { for (Node<E> x = last; x != null; x = x.prev) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = last; x != null; x = x.prev) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; }
它們的特殊之處在於,它們想要刪除的結點的數值也許有很多個,但是它們只會刪除第一個出現的或者是最後一個出現的。
然後我們看一下搜索元素的方法:
//判斷是否包含某個特定的結點的值 public boolean contains(Object o) { return indexOf(o) != -1; } //查找LinkedList中是否包含某個值,並返回第一個出現這個值的索引值,否則返回-1 public int indexOf(Object o) { int index = 0; if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; } //反向查找LinkedList中是否包含某個值,並返回第一個出現這個值的索引值,否則返回-1 public int lastIndexOf(Object o) { int index = size; if (o == null) { for (Node<E> x = last; x != null; x = x.prev) { index--; if (x.item == null) return index; } } else { for (Node<E> x = last; x != null; x = x.prev) { index--; if (o.equals(x.item)) return index; } } return -1; }
可以看出來,搜索元素是比較麻煩的,必須要全部遍歷一遍。
最後我們看一下一些邊界值判斷的方法:
//判斷某個索引值是否存在 private boolean isElementIndex(int index) { return index >= 0 && index < size; } //判斷這個索引是否超出了位置的邊界,這個和上面的有何區別?為何index是<=而不是< private boolean isPositionIndex(int index) { return index >= 0 && index <= size; } //多種邊界異常的判斷 private String outOfBoundsMsg(int index) { return "Index: "+index+", Size: "+size; } private void checkElementIndex(int index) { if (!isElementIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private void checkPositionIndex(int index) { if (!isPositionIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
限於篇幅(Lan),其他方法就不一一介紹了。
總結:
1.LinkedList的本質基於雙向鏈表實。
2.LinkedList在查找元素時,必須遍歷鏈表;在新增和刪除元素時,只要調整前後的引用就可以了。
3.LinkedList不是線程安全的,同樣擁有fast-fail機制。
【Java集合】試讀LinkedList源碼