LinkedList
是基於雙向鏈表來編寫的,不須要考慮容量的問題,但節點須要額外的空間存儲前驅和後繼的引用。有序可重複,可存儲null
值,非線程安全。java
LinkedList
實現了Deque
接口,這樣LinkedList
就具有了雙端隊列的功能,本文旨在介紹LinkedList
的List
功能,隊列功能再也不進行說明。node
LinkedList
沒有實現RandomAccess
接口,因此不要經過訪問下標方式(for(int i=0;i<size;i++)
)遍歷,不然效率極差。安全
本文主要針對LinkedList
的經常使用操做進行分析,代碼以下。dom
List<String> linkedList = new LinkedList<>(); //add(E e) linkedList.add("QQ"); linkedList.add("WW"); linkedList.add("EE"); linkedList.add("RR"); linkedList.add("TT"); linkedList.add("YY"); linkedList.add("UU"); linkedList.add("II"); linkedList.add("OO"); //add(int index, E element)插入元素到指定結點 linkedList.add(2, "BaseC"); //get System.out.println(linkedList.get(2)); //traverse(遍歷) Iterator<String> iterator = linkedList.iterator(); while (iterator.hasNext()) { iterator.next(); } //remove(Object o) linkedList.remove("EE"); //remove(int index)刪除中間元素 linkedList.remove(3); //remove(int index)刪除鏈表頭元素 linkedList.remove(0); //remove(int index)刪除鏈表尾元素 linkedList.remove(linkedList.size() - 1); System.out.println(linkedList);
//元素個數 transient int size = 0; //鏈表首結點 transient Node<E> first; //鏈表尾結點 transient Node<E> last; //鏈表結點類 private static class Node<E> { //結點中的值 E item; //指向以前的節點 Node<E> prev; //指向以後的節點 Node<E> next; //構造方法 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
add(E e)
//將元素添加到列表尾部 public boolean add(E e) { linkLast(e); return true; } //將e元素添加鏈表末端 void linkLast(E e) { //聲明l變量保存當前last對象 final Node<E> l = last; //以e爲item聲明一個新的last對象 final Node<E> newNode = new Node<>(l, e, null); last = newNode; //第一次添加元素時 if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
首次添加元素以下圖所示:源碼分析
非首次添加元素以下如圖所示:post
add(int index, E element)
//將元素插入到指定index,以前在此index及其以後的元素向右移動。 public void add(int index, E element) { //判斷index是否在[0,size]之間,注意包含0和size。 checkPositionIndex(index); //若是index等於當前size,調用linkLast(E e)方法,不然調用linkBefore(E e)方法 if (index == size) linkLast(element); else linkBefore(element, node(index)); } //返回指定index上的非空結點 Node<E> node(int index) { //假設index合法 //size>>1返回size的一半,判斷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; } } //在非空的succ結點以前插入元素 void linkBefore(E e, Node<E> succ) { //獲取succ結點的prev結點 final Node<E> pred = succ.prev; //在pred和succ之間生成item爲e的結點 final Node<E> newNode = new Node<>(pred, e, succ); //將succ的prev指向newNode succ.prev = newNode; //若是succ是頭元素,將first指向newNode if (pred == null) first = newNode; else //不然將pred的next結點指向newNode pred.next = newNode; size++; modCount++; }
添加流程以下圖所示:this
在鏈表首尾添加元素很高效,時間複雜度爲O(1)
。spa
在鏈表中間添加元素比較低效,時間複雜度爲O(N)
。線程
//返回指定位置上的值 public E get(int index) { //檢查index是否合法 checkElementIndex(index); //node(int index)方法已在上文列出,此處不在展現。 return node(index).item; }
查找方法的重點在於node(int index)
方法。因爲須要經過從頭或從尾查找元素,時間複雜度爲O(N)
。3d
在分析源碼以前,先看下iterator()
的調用流程。首先調用linkedList.iterator()
進入AbstractSequentialList
的iterator()
方法。
public Iterator<E> iterator() { return listIterator(); }
此方法調用了其父類AbstractList
的listIterator()
方法。
public ListIterator<E> listIterator() { return listIterator(0); }
而在此方法中調用了listIterator(final int index)
方法,此方法LinkedList
將其重寫了,因此程序就會去調用LinkedList
中的listIterator(int index)
方法。經過這個流程咱們也鞏固下Java
多繼承下方法的調用流程。下面我們就來看源碼吧。
public ListIterator<E> listIterator(int index) { //判斷index是否在[0,size]之間,注意包含0和size。 checkPositionIndex(index); return new ListItr(index); } private class ListItr implements ListIterator<E> { //上一次返回的值 private Node<E> lastReturned; //當前要返回的值 private Node<E> next; //當前index值 private int nextIndex; //用於fail-fast校驗 private int expectedModCount = modCount; ListItr(int index) { //若是index是當前size值,則next爲null,不然返回對應index的結點。 next = (index == size) ? null : node(index); nextIndex = index; } public boolean hasNext() { return nextIndex < size; } public E next() { //fail-fast判斷 checkForComodification(); //判斷當前是否還有值,調用方可直接調用next()方法獲取下個值,沒必要額外進行hasNext()的判斷。 if (!hasNext()) throw new NoSuchElementException(); //即將返回next,將next傳給lastReturned lastReturned = next; //獲取下次將要返回的結點 next = next.next; nextIndex++; return lastReturned.item; } //省略其他代碼。。。 }
remove(Object o)
//刪除o在列表中第一次存儲的位置 public boolean remove(Object o) { //當o爲null時 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; } //刪除非空結點 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結點,將被刪除結點的prev置爲null prev.next = next; x.prev = null; } //刪除尾元素時 if (next == null) { last = prev; } else { //將next結點的prev指針指向prev結點,將被刪除結點的next置爲null next.prev = prev; x.next = null; } //將x的item變爲null,便於GC x.item = null; size--; modCount++; return element; }
unlink(Node x)
其流程以下圖所示:
remove(int index)
//刪除指定index處的元素,後面的元素向左移動一位,返回被刪除的元素。 public E remove(int index) { //檢驗index是否在[0,index)以內 checkElementIndex(index); //經過node(int index)查找結點,將其傳入unlink(Node node)方法 return unlink(node(index)); }
在鏈表首尾刪除元素很高效,時間複雜度爲O(1)
。
在鏈表中間刪除元素比較低效,時間複雜度爲O(N)
。