JDK原始碼筆記-LinkedList
1、概述
LinkedList ,基於節點實現的雙向連結串列的 List ,每個節點都指向前一個和後一個節點從而形成連結串列。
相比 ArrayList 來說,我們日常開發使用 LinkedList 相對比較少
2、類圖
LinkedList 實現的介面、繼承的抽象類,如下圖所示:
如下 3 個介面是 ArrayList 一致的:
如下 1 個介面是少於 ArrayList 的:
java.util.RandomAccess
介面,LinkedList 不同於 ArrayList 的很大一點,不支援隨機訪問。
如下 1 個介面是多於 ArrayList 的:
-
java.util.Deque
介面,提供雙端佇列的功能,LinkedList 支援快速的在頭尾新增元素和讀取元素,所以很容易實現該特性。
繼承了java.util.AbstractSequentialList
抽象類,它是 AbstractList 的子類,實現了只能連續訪問“資料儲存”(例如說連結串列)的#get(int index)
、#add(int index, E element)
等等隨機操作的方法
3、屬性
LinkedList 一共有3個屬性。如下圖所示:
- 通過 Node 節點指向前後節點,從而形成雙向連結串列。
first
和last
屬性:連結串列的頭尾指標。- 在初始時候,
first
和last
指向null
,因為此時暫時沒有 Node 節點。 - 在新增完首個節點後,建立對應的 Node 節點
node1
,前後指向null
。此時,first
和last
指向該 Node 節點。 - 繼續新增一個節點後,建立對應的 Node 節點
node2
,其prev = node1
和next = null
,而node1
的prev = null
和next = node2
。此時,first
保持不變,指向node1
,last
發生改變,指向node2
。
- 在初始時候,
size
屬性:連結串列的節點數量。通過它進行計數,避免每次需要 List 大小時,需要從頭到尾的遍歷。
/** * 連結串列大小 */ transient int size = 0; /** * 頭節點 * * Pointer to first node. */ transient Node<E> first; /** * 尾節點 * * Pointer to last node. */ transient Node<E> last; /** * 節點 * * @param <E> 元素泛型 */ 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; } }
4、提供的方法
(1)構造方法
LinkedList 一共有兩個構造方法,我們分別來看看
public LinkedList() { } public LinkedList(Collection<? extends E> c) { this(); // 新增 c 到連結串列中 addAll(c); }
相比 ArrayList 來說,因為沒有容量一說,所以不需要提供#ArrayList(int initialCapacity)
這樣的構造方法。
(2)新增單個元素
#add(E e)
方法,順序新增單個元素到連結串列。程式碼如下:
public boolean add(E e) { //新增到末尾 linkLast(e); return true; } void linkLast(E e) { //記錄原last節點 final Node<E> l = last; //建立新節點 // l 是newNode前一個節點 // e 是節點元素 //null 是newNode的下一個節點 final Node<E> newNode = new Node<>(l, e, null); //last指向新節點 last = newNode; //如果 l 為空,則first節點也為空,那麼first指向新節點 if (l == null) first = newNode; //如果 l 不為空,則first節點也不為空,那麼last指向新節點 else l.next = newNode; //連結串列長度+1 size++; //運算元+1 modCount++; }
#add(int index, E element)
方法,插入單個元素到指定位置
public void add(int index, E element) { //校驗是否超出範圍 checkPositionIndex(index); //如果index = size,就是插入到尾部 if (index == size) //實際上就是add 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指向新節點 first = newNode; else //否則前節點的後節點指向新節點 pred.next = newNode; size++; modCount++; }
#node(int index)
方法,獲得第index
個 Node 節點
Node<E> node(int index) { // assert isElementIndex(index); //size >> 1 位運算 結果大概為size的一半 //如果index在前半部分,正序遍歷,獲取第index的節點 if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { //在後半部分,就倒序遍歷,獲取第index的節點 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
#linkBefore(E e, Node<E> succ)
方法,新增元素e
到succ
節點的前面,與linkLast邏輯基本相同
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 實現了 Deque 介面,所以它實現了#addFirst(E e)
和#addLast(E e)
方法,分別新增元素到連結串列的頭尾
public void addFirst(E e) { linkFirst(e); } public boolean offerFirst(E e) { addFirst(e); // 呼叫上面的方法 return true; } public void addLast(E e) { linkLast(e); } public boolean offerLast(E e) { addLast(e); // 呼叫上面的方法 return true; }
linkLast方法就不多說了,下面是#linkFirst(E e)
方法,新增元素到隊頭
private void linkFirst(E e) { //記錄first節點 final Node<E> f = first; //建立新節點 final Node<E> newNode = new Node<>(null, e, f); //first指向新節點 first = newNode; //如果first節點為空,則last節點也為空,那麼last也指向新節點 if (f == null) last = newNode; else //原first節點的前節點指向新節點 f.prev = newNode; size++; modCount++; }
整體來說,新增單個元素,分為三個方法:
- 新增到隊頭
- 新增到隊尾
- 新增到中間
(3)新增多個元素
#addAll(Collection<? extends E> 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; //新建兩個節點,之後操作 Node<E> pred, succ; //如果index==size,那麼就相當於在隊尾新增,則succ=null,pred=las //如果index不等於size,那麼就相當於在中間新增,則succ為index位置的節點,pred為index的前節點 if (index == size) { succ = null; pred = last; } else { succ = node(index); pred = succ.prev; } for (Object o : a) { //建立新節點 @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null); //判斷pred是否為null,如果為null,那麼pred也為null,那麼first指向新節點 //如果不為null,index位置的前一個節點指向新節點 if (pred == null) first = newNode; else pred.next = newNode; //pred指向新節點 pred = newNode; } //如果succ為null,那麼last也為null,則last指向pred //不為null,那麼pred的next指向後節點,後節點的prev指向前節點 if (succ == null) { last = pred; } else { pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true; }
(4)移除單個元素
#remove(int index)
方法,移除指定位置的元素,並返回該位置的原元素
public E remove(int index) { checkElementIndex(index); //呼叫node方法獲得第index位置的節點,然後刪除 return unlink(node(index)); } E unlink(Node<E> x) { // assert x != null; //拿到x的元素 final E element = x.item; //拿到x的後節點 final Node<E> next = x.next; //拿到x的前節點 final Node<E> prev = x.prev; //如果x的前節點為null,那麼將first指向x的後節點,可以理解為移除隊頭元素 //不為null,那麼將x的前節點的next指向x的後節點,並將x的prev置null if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } //和前節點部分同裡,只不過後節點為null時,為移除隊尾元素 if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
#remove(Object o)
方法,移除首個為o
的元素,並返回是否移除到
public boolean remove(Object o) { //元素為null時,遍歷連結串列,找到null的元素,刪除 //不為null時,遍歷連結串列,找的和o元素相等的節點,刪除 if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; }
#removeFirstOccurrence(Object o)
和#removeLastOccurrence(Object o)
方法,分別實現移除連結串列首個節點和最後節點
public boolean removeFirstOccurrence(Object o) { // 移除首個 return remove(o); } public boolean removeLastOccurrence(Object o) { if (o == null) { // o 為 null 的情況 // 倒序遍歷,找到 null 的元素後,進行移除 for (Node<E> x = last; x != null; x = x.prev) { if (x.item == null) { unlink(x); return true; } } } else { // 倒序遍歷,找到等於 o 的元素後,進行移除 for (Node<E> x = last; x != null; x = x.prev) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; }
#remove()
方法,移除連結串列首個節點
public E remove() { return removeFirst(); } public E removeFirst() { final Node<E> f = first; //連結串列為空時,丟擲異常 if (f == null) throw new NoSuchElementException(); //移除連結串列首個節點 return unlinkFirst(f); } private E unlinkFirst(Node<E> f) { // assert f == first && f != null; //拿到首個節點元素 final E element = f.item; //拿到首個節點指向的後節點 final Node<E> next = f.next; //元素置null,後節點指向置null f.item = null; f.next = null; // help GC //first指向首個節點指向的後節點 first = next; //如果後節點為null,那麼整個連結串列時null的,那麼last也為null //不為空的話,後節點的前節點指向置空 if (next == null) last = null; else next.prev = null; size--; modCount++; return element; }
#removeLast()
方法,移除連結串列最後一個節點,和#
removeFirst()方法思路是差不多的
public E removeLast() { final Node<E> l = last; // 如果連結串列為空,則丟擲 NoSuchElementException 移除 if (l == null) throw new NoSuchElementException(); // 移除連結串列的最後一個元素 return unlinkLast(l); } private E unlinkLast(Node<E> l) { // assert l == last && l != null; final E element = l.item; // 獲得 f 的上一個節點 final Node<E> prev = l.prev; // 設定 l 的 item 為 null ,幫助 GC l.item = null; // 設定 l 的 prev 為 null ,幫助 GC l.prev = null; // help GC // 修改 last 指向 prev last = prev; // 修改 prev 節點的 next 指向 null if (prev == null) // 如果連結串列只有一個元素,說明被移除後,佇列就是空的,則 first 設定為 null first = null; else prev.next = null; // 連結串列大小減一 size--; // 增加陣列修改次數 modCount++; return element; }
#poll()
和#
pop()方法,移除連結串列的頭或尾,差異點在於連結串列為空時候,不會丟擲 NoSuchElementException 異常
public E poll() { // 移除頭 final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } public E pop() { return removeFirst(); } // LinkedList.java 實現 Deque 介面 public E pollFirst() { // 移除頭 final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } public E pollLast() { // 移除尾 final Node<E> l = last; return (l == null) ? null : unlinkLast(l); }
(5)移除多個元素
#removeAll(Collection<?> c)
方法,批量移除指定的多個元素
public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); boolean modified = false; // 獲得迭代器 Iterator<?> it = iterator(); // 通過迭代器遍歷 while (it.hasNext()) { // 如果 c 中存在該元素,則進行移除 if (c.contains(it.next())) { it.remove(); modified = true; // 標記修改 } } return modified; }
#retainAll(Collection<?> c)
方法,求 LinkedList 和指定多個元素的交集。簡單來說,恰好和#removeAll(Collection<?> c)
相反,移除不在c
中的元素
// AbstractCollection.java public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); boolean modified = false; // 獲得迭代器 Iterator<E> it = iterator(); // 通過迭代器遍歷 while (it.hasNext()) { // <X> 如果 c 中不存在該元素,則進行移除 if (!c.contains(it.next())) { it.remove(); modified = true; } } return modified; }
(6)查詢單個元素
#indexOf(Object o)
方法,查詢首個為指定元素的位置
public int indexOf(Object o) { int index = 0; if (o == null) {//如果o為null //迴圈遍歷 for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index;//找到返回 index++; } } else {//如果o不為null //迴圈遍歷 for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index;//找到返回 index++; } } return -1; }
而#contains(Object o)
方法,就是基於該方法實現
public boolean contains(Object o) { return indexOf(o) >= 0; }
有時我們需要查詢最後一個為指定元素的位置,所以會使用到#lastIndexOf(Object o)
方法
public int lastIndexOf(Object o) { int index = size; if (o == null) { // 如果 o 為 null 的情況 // 倒序遍歷,如果 item 為 null 的節點,進行返回 for (Node<E> x = last; x != null; x = x.prev) { index--; if (x.item == null) return index; // 找到 } } else { // 如果 o 非 null 的情況 // 倒序遍歷,如果 item 為 o 的節點,進行返回 for (Node<E> x = last; x != null; x = x.prev) { index--; if (o.equals(x.item)) return index; // 找到 } } // 未找到 return -1; }
(7)獲得指定位置的元素
#get(int index)
方法,獲得指定位置的元素
public E get(int index) { checkElementIndex(index); // 基於 node(int index) 方法實現 return node(index).item; }
因為 LinkedList 實現了 Deque 介面,所以它實現了#peekFirst()
和#peekLast()
方法,分別獲得元素到連結串列的頭尾
public E peekFirst() { final Node<E> f = first; return (f == null) ? null : f.item; } public E peekLast() { final Node<E> l = last; return (l == null) ? null : l.item; }
因為 LinkedList 實現了 Queue 介面,所以它實現了#peek()
和#element()
方法,分別獲得元素到連結串列的頭
public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } public E element() { // 如果連結串列為空識,丟擲 NoSuchElementException 異常 return getFirst(); } public E getFirst() { final Node<E> f = first; if (f == null) // 如果連結串列為空識,丟擲 NoSuchElementException 異常 throw new NoSuchElementException(); return f.item; }
(8)獲取指定位置的元素
#set(int index, E element)
方法,設定指定位置的元素
public E set(int index, E element) { checkElementIndex(index); //獲取index的節點 Node<E> x = node(index); E oldVal = x.item; //設定index節點的元素 x.item = element; return oldVal; }
(9)轉換為陣列
#toArray()
方法,將 ArrayList 轉換成[]
陣列
public Object[] toArray() { //相同長度的新陣列 Object[] result = new Object[size]; int i = 0; //迴圈設定陣列中對應位置的值 for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; return result; }
實際場景下,我們可能想要指定T
泛型的陣列,那麼我們就需要使用到#toArray(T[] a)
方法
public <T> T[] toArray(T[] a) { //如果傳入陣列長度比size小,直接複製一個數組返回 if (a.length < size) a = (T[])java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), size); int i = 0; Object[] result = a; //迴圈遍歷賦值 for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; //如果傳入的陣列長度大於size,那麼將size位置置null if (a.length > size) a[size] = null; return a; }
(10)求雜湊值
#hashCode()
方法,求 LinkedList 的雜湊值
public int hashCode() { int hashCode = 1; // 遍歷,求雜湊 for (E e : this) hashCode = 31*hashCode + (e==null ? 0 : e.hashCode()); return hashCode; }
(11)判斷相等
#equals(Object o)
方法,判斷是否相等
public boolean equals(Object o) { // 如果 o 就是自己,直接返回 true if (o == this) return true; // 如果不為 List 型別,直接返回 false if (!(o instanceof List)) return false; // 建立迭代器,順序遍歷比對 ListIterator<E> e1 = listIterator(); ListIterator<?> e2 = ((List<?>) o).listIterator(); while (e1.hasNext() && e2.hasNext()) { E o1 = e1.next(); Object o2 = e2.next(); if (!(o1==null ? o2==null : o1.equals(o2))) // 如果不相等,返回 false return false; } // 如果有迭代器沒有遍歷完,說明兩者長度不等,所以就不相等;否則,就相等了 return !(e1.hasNext() || e2.hasNext()); }
(12)清空連結串列
public void clear() { // Clearing all of the links between nodes is "unnecessary", but: // - helps a generational GC if the discarded nodes inhabit // more than one generation // - is sure to free memory even if there is a reachable Iterator for (Node<E> x = first; x != null; ) { //拿到x的後節點 Node<E> next = x.next; //將前後節點指向和元素全部置空 x.item = null; x.next = null; x.prev = null; //x指向x的後節點 x = next; } first = last = null; size = 0; modCount++; }
(13)克隆
#clone()
方法,克隆 LinkedList 物件
public Object clone() { //呼叫父類clone方法 LinkedList<E> clone = superClone(); // Put clone into "virgin" state //將clone重置為初始狀態,此處注意,first、last等都是重新初始化的,不與前LinkedList共享 clone.first = clone.last = null; clone.size = 0; clone.modCount = 0; // Initialize clone with our elements //迴圈遍歷新增節點 for (Node<E> x = first; x != null; x = x.next) clone.add(x.item); return clone; }
(14)建立子陣列
#subList(int fromIndex, int toIndex)
方法,建立 ArrayList 的子陣列
public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size()); // 根據判斷 RandomAccess 介面,判斷是否支援隨機訪問 return (this instanceof RandomAccess ? new RandomAccessSubList<>(this, fromIndex, toIndex) : new SubList<>(this, fromIndex, toIndex)); }
- 該方法,是通過父類 AbstractList 來實現的。
- 根據判斷 RandomAccess 介面,判斷是否支援隨機訪問,從而建立 RandomAccessSubList 或 SubList 物件
(15)建立Iterator迭代器
#iterator()
方法,建立迭代器
public Iterator<E> iterator() { return listIterator(); } // AbstractList.java public ListIterator<E> listIterator() { return listIterator(0); } // AbstractSequentialList.java public abstract ListIterator<E> listIterator(int index);
- 該方法,是通過父類 AbstractSequentialList 來實現的。
- 整個呼叫過程是,
iterator() => listIterator() => listIterator(int index)
的順序,就是我們在程式碼裡貼進去的順序。最終呢,是呼叫 LinkedList 對#listIterator(int index)
的實現
(16)建立LIstIterator迭代器
#listIterator(int index)
方法,建立 ListIterator 迭代器
public ListIterator<E> listIterator(int index) { checkPositionIndex(index); return new ListItr(index); }
因為 ListItr 的實現程式碼比較簡單,我們就不逐個來看了,直接貼加了註釋的程式碼
private class ListItr implements ListIterator<E> { /** * 最後返回的節點 */ private Node<E> lastReturned; /** * 下一個節點 */ private Node<E> next; /** * 下一個訪問元素的位置,從下標 0 開始。 * * 主要用於 {@link #nextIndex()} 中,判斷是否遍歷結束 */ private int nextIndex; /** * 建立迭代器時,陣列修改次數。 * * 在迭代過程中,如果陣列發生了變化,會丟擲 ConcurrentModificationException 異常。 */ private int expectedModCount = modCount; ListItr(int index) { // assert isPositionIndex(index); // 獲得下一個節點 next = (index == size) ? null : node(index); // 下一個節點的位置 nextIndex = index; } public boolean hasNext() { return nextIndex < size; } public E next() { // 校驗是否陣列發生了變化 checkForComodification(); // 如果已經遍歷到結尾,丟擲 NoSuchElementException 異常 if (!hasNext()) throw new NoSuchElementException(); // lastReturned 指向,記錄最後訪問節點 lastReturned = next; // next 指向,下一個節點 next = next.next; // 下一個節點的位置 + 1 nextIndex++; // 返回 lastReturned return lastReturned.item; } public boolean hasPrevious() { return nextIndex > 0; } public E previous() { // 校驗是否陣列發生了變化 checkForComodification(); // 如果已經遍歷到結尾,丟擲 NoSuchElementException 異常 if (!hasPrevious()) throw new NoSuchElementException(); // 修改 lastReturned 和 next 的指向。此時,lastReturned 和 next 是相等的。 lastReturned = next = (next == null) ? last : next.prev; // 下一個節點的位置 - 1 nextIndex--; // 返回 lastReturned return lastReturned.item; } public int nextIndex() { return nextIndex; } public int previousIndex() { return nextIndex - 1; } public void remove() { // 校驗是否陣列發生了變化 checkForComodification(); // 如果 lastReturned 為空,丟擲 IllegalStateException 異常,因為無法移除了。 if (lastReturned == null) throw new IllegalStateException(); // 獲得 lastReturned 的下一個 Node<E> lastNext = lastReturned.next; // 移除 lastReturned 節點 unlink(lastReturned); // 此處,會分成兩種情況 if (next == lastReturned) // 說明發生過呼叫 `#previous()` 方法的情況,next 指向下一個節點,而 nextIndex 是無需更改的 next = lastNext; else nextIndex--; // nextIndex 減一。 // 設定 lastReturned 為空 lastReturned = null; // 增加陣列修改次數 expectedModCount++; } public void set(E e) { // 如果 lastReturned 為空,丟擲 IllegalStateException 異常,因為無法修改了。 if (lastReturned == null) throw new IllegalStateException(); // 校驗是否陣列發生了變化 checkForComodification(); // 修改 lastReturned 的 item 為 e lastReturned.item = e; } public void add(E e) { // 校驗是否陣列發生了變化 checkForComodification(); // 設定 lastReturned 為空 lastReturned = null; // 此處,會分成兩種情況 if (next == null) // 如果 next 已經遍歷到尾,則 e 作為新的尾節點,進行插入。算是效能優化 linkLast(e); else // 插入到 next 的前面 linkBefore(e, next); // nextIndex 加一。 nextIndex++; // 增加陣列修改次數 expectedModCount++; } public void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); // 遍歷剩餘連結串列 while (modCount == expectedModCount && nextIndex < size) { // 執行 action 邏輯 action.accept(next.item); // lastReturned 指向 next lastReturned = next; // next 指向下一個節點 next = next.next; // nextIndex 加一。 nextIndex++; } // 校驗是否陣列發生了變化 checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }