Java集合原始碼學習(3)LinkedList
ArrayList,陣列是順序儲存結構,儲存區間是連續的,佔用記憶體嚴重,故空間複雜的很大。但陣列的二分查詢時間複雜度小,為O(1),陣列的特點是定址容易,插入和刪除困難。 LinkedList使用連結串列作為儲存結構,連結串列是線性儲存結構,在記憶體上不是連續的一段空間,佔用記憶體比較寬鬆,故空間複雜度很小,但時間複雜度很大,達O(N),連結串列的特點是定址困難,插入和刪除容易。
在JDK1.7之前,LinkedList是採用雙向環形連結串列來實現的,在1.7及之後,Oracle將LinkedList做了優化,將環形連結串列改成了線性連結串列。
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable 複製程式碼
LinkedList繼承了AbstractSequentialList,實現了List,Deque,Cloneable,Serializable 介面
(1)繼承和實現
繼承AbstractSequentialList類,提供了相關的新增、刪除、修改、遍歷等功能。
實現List介面,提供了相關的新增、刪除、修改、遍歷等功能。 實現 Deque 介面,即能將LinkedList當作雙端佇列使用,可以用做佇列或者棧。 實現了Cloneable介面,即覆蓋了函式clone(),能被克隆。 實現java.io.Serializable介面,LinkedList支援序列化,能通過序列化傳輸。
(2)執行緒安全
LinkedList是非同步的,即執行緒不安全,如果有多個執行緒同時訪問LinkedList,可能會丟擲ConcurrentModificationException異常。
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } 複製程式碼
(3)節點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; } } 複製程式碼
(4)addAll方法:
//addAll ,在尾部批量增加 複製程式碼
public boolean addAll(Collection<? extends E> c) { return addAll(size, c);//以size為插入下標,插入集合c中所有元素 } //以index為插入下標,插入集合c中所有元素 public boolean addAll(int index, Collection<? extends E> c) { checkPositionIndex(index);//檢查越界 [0,size] 閉區間
Object[] a = c.toArray();//拿到目標集合陣列 int numNew = a.length;//新增元素的數量 if (numNew == 0)//如果新增元素數量為0,則不增加,並返回false return false; Node<E> pred, succ;//index節點的前置節點,後置節點 if (index == size) { //在連結串列尾部追加資料 succ = null;//size節點(隊尾)的後置節點一定是null pred = last;//前置節點是隊尾 } else { succ = node(index);//取出index節點,作為後置節點 pred = succ.prev; //前置節點是,index節點的前一個節點 } //連結串列批量增加,是靠for迴圈遍歷原陣列,依次執行插入節點操作。對比ArrayList是通過System.arraycopy完成批量增加的 for (Object o : a) {//遍歷要新增的節點。 @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null);//以前置節點 和 元素值e,構建new一個新節點, if (pred == null) //如果前置節點是空,說明是頭結點 first = newNode; else//否則 前置節點的後置節點設定問新節點 pred.next = newNode; pred = newNode;//步進,當前的節點為前置節點了,為下次新增節點做準備 } if (succ == null) {//迴圈結束後,判斷,如果後置節點是null。 說明此時是在隊尾append的。 last = pred; //則設定尾節點 } else { pred.next = succ; // 否則是在隊中插入的節點 ,更新前置節點 後置節點 succ.prev = pred; //更新後置節點的前置節點 } size += numNew;// 修改數量size modCount++;//修改modCount return true; } //根據index 查詢出Node, Node<E> node(int index) { // assert isElementIndex(index); //通過下標獲取某個node 的時候,(增、查 ),會根據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; } } private void checkPositionIndex(int index) { if (!isPositionIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private boolean isPositionIndex(int index) { return index >= 0 && index <= size;//插入時的檢查,下標可以是size [0,size] } 複製程式碼
在構造方法中呼叫addAll方法,相當於是向一個空連結串列中新增集合c中的元素。 如果是在已有元素的連結串列中呼叫addAll方法來新增元素的話,就需要判斷指定的新增位置index是否越界,如果越界會丟擲異常;如果沒有越界,根據新增的位置index,斷開連結串列中index位置的節點前後的引用,加入新元素,重新連上斷開位置的前後節點的引用。過程如下圖:(此處用了xiaoyanger的圖)

(5)toArray()方法:
public Object[] toArray() { //new 一個新陣列 然後遍歷連結串列,將每個元素存在數組裡,返回 Object[] result = new Object[size]; int i = 0; for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; return result; } 複製程式碼
這個方法的實現很簡單。
(6)總結:
LinkedList 是雙向列表。
連結串列批量增加,是靠for迴圈遍歷原陣列,依次執行插入節點操作。對比ArrayList是通過System.arraycopy完成批量增加的。增加一定會修改modCount。 通過下標獲取某個node 的時候,(add select),會根據index處於前半段還是後半段 進行一個折半,以提升查詢效率 刪也一定會修改modCount。 按下標刪,也是先根據index找到Node,然後去連結串列上unlink掉這個Node。 按元素刪,會先去遍歷連結串列尋找是否有該Node,如果有,去連結串列上unlink掉這個Node。 改也是先根據index找到Node,然後替換值。改不修改modCount。 查本身就是根據index找到Node。 所以它的CRUD(Create,Retrieve,Update,Delete)操作裡,都涉及到根據index去找到Node的操作。