B 站 COO 李旎:尊重創作規律和創作者,才能做出好內容
Java集合框架實現兩個兩種線性表,第一種是陣列的實現ArrayList,第二種則是連結串列的實現LinkedList。作為連結串列,LinkedList具有增加和刪除效率高的特點,但是其也有缺點,就是無法隨機訪問。
LinkedList實現了List, Deque,Cloneable以及Serializable介面。說明使用LinkedList不僅可以使用基本的連結串列操作,同時還能使用雙端佇列以及棧的操作。
LinkedList的結點實現是其內部定義的私有靜態類Node。Node類程式碼如下:
1 private static class Node<E> { 2 E item; 3 Node<E> next; 4 Node<E> prev; 5 6 Node(Node<E> prev, E element, Node<E> next) { 7 this.item = element; 8 this.next = next; 9 this.prev = prev; 10 }11 }
很顯然從這裡我們能知道,LinkedList實際上是雙鏈表的實現。
1 transient int size = 0; 2 transient Node<E> first; 3 transient Node<E> last;
size表示連結串列裡結點的數量,first指向連結串列的頭結點,last指向連結串列的尾結點。
1 public LinkedList(Collection<? extends E> c) { 2 this(); 3 addAll(c); 4 } 5 public boolean addAll(Collection<? extends E> c) { 6 return addAll(size, c); 7 }
首先檢查index是否合法,之後集合c變成Object陣列,檢查Object陣列的大小,如果為0則新增失敗。之後找到index位置的連結串列,從index位置開始插入,最後連線上原本的連結串列結點。
public boolean addAll(int index, Collection<? extends E> c) { //檢查下標是否合法 checkPositionIndex(index); //將集合c轉換成Object陣列 Object[] a = c.toArray(); int numNew = a.length; //檢查Object陣列長度,如果為0說明沒有需要新增的元素 if (numNew == 0) return false; //succ指向下標對應的結點,pred則是succ的前驅 Node<E> pred, succ; //如果下標正好是連結串列長度,則 if (index == size) { succ = null; pred = last; } else { //呼叫node方法返回特定下標的結點,查詢方法首先根據下標是否超過連結串列長度一半,超過或者等於連結串列長度一半 //則從尾結點last開始,從尾向index查詢,如果沒有超過連結串列長度一半,則從頭結點first開始,從頭向index找 succ = node(index); pred = succ.prev; } //開始將Obejct陣列的元素插入到index之後,首先new一個新的結點,其後繼是pred,之後檢查pred如果是空,說明此時newNode是頭結點(?),如果pred非空 //則按照如圖2.1的方式連線雙鏈表的兩個結點,一直迴圈直到Object數組裡的元素都被連線上。 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; } //如果succ為空,說明是在連結串列的結尾插入集合裡的所有元素,則last指向pred if (succ == null) { last = pred; } else { //否則直接連線之前連結串列裡的元素 pred.next = succ; succ.prev = pred; } //修改連結串列的長度 size += numNew; // modCount++; return true; }
add方法有兩個過載的版本,一個是隻有一個泛型引數的版本,也是最常用的版本,另外一個是往特定位置index插入的版本,下面分析最常用的版本。
add(E e)方法將元素用尾插法插入到連結串列的結尾。首先new一個前驅為last的新節點newNode,之後last指向新的尾結點newNode,最後檢查之前的尾結點如果為空,則新生成的結點作為頭結點,否則將原本的尾結點的後繼指向newNode,並給連結串列的長度加一
1 public boolean add(E e) { 2 linkLast(e); 3 return true; 4 } 5 void linkLast(E e) { 6 final Node<E> l = last; 7 final Node<E> newNode = new Node<>(l, e, null); 8 last = newNode; 9 if (l == null) 10 first = newNode; 11 else 12 l.next = newNode; 13 size++; 14 modCount++; 15 }
1 public E get(int index) { 2 checkElementIndex(index); 3 return node(index).item; 4 }
首先檢查下標是否合法,合法的話呼叫之前分析addAll時出現過的方法node(int index),這個方法返回特定下標的結點,根據下標是否超過連結串列大小的一半來決定,之前已經分析過了,就不再贅述。故在需要大量隨機訪問的情況下,還是推薦使用ArrayList實現,當然,如果連結串列長度不是很長,那也無所謂。
remove方法刪除一個與引數相同的元素,準確的說是在連結串列裡刪除下標最小的與引數相同的元素。