LinkedList是用鏈表結構存儲數據的,比較適合數據的動態插入和刪除,隨機訪問和遍歷速度比較慢,還提供了List介面i中沒有定義的方法,專門用於操作表頭和表尾的元素,所以可以當作堆棧、隊列和雙向隊列來使用。LInkedList持有頭節點和尾節點的引用,有兩個構造器,一個是無參構造器,另一個是傳入 ...
LinkedList是用鏈表結構存儲數據的,比較適合數據的動態插入和刪除,隨機訪問和遍歷速度比較慢,還提供了List介面i中沒有定義的方法,專門用於操作表頭和表尾的元素,所以可以當作堆棧、隊列和雙向隊列來使用。LInkedList持有頭節點和尾節點的引用,有兩個構造器,一個是無參構造器,另一個是傳入外部集合構造器,它沒有像ArrayList一樣的初始大小的構造器。
1 //集合元素個數 2 3 transient int size = 0; 4 5 6 7 //頭結點引用 8 9 transient Node<E> first; 10 11 12 13 //尾節點引用 14 transient Node<E> last; 15 16 //無參構造器 17 public LinkedList() {} 18 19 //傳入外部集合的構造器 20 public LinkedList(Collection<? extends E> c) { 21 this(); 22 addAll(c); 23 } 24 25 //增(添加) 26 public boolean add(E e) { 27 28 //在鏈表尾部添加 29 linkLast(e); 30 return true; 31 } 32 33 34 //增(插入) 35 public void add(int index, E element) { 36 37 checkPositionIndex(index); 38 39 if (index == size) { 40 //在鏈表尾部添加 41 linkLast(element); 42 } else { 43 //在鏈表中部插入 44 linkBefore(element, node(index)); 45 } 46 } 47 48 49 50 //刪(給定下標) 51 public E remove(int index) { 52 53 //檢查下標是否合法 54 checkElementIndex(index); 55 return unlink(node(index)); 56 } 57 58 //刪(給定元素) 59 public boolean remove(Object o) { 60 if (o == null) { 61 for (Node<E> x = first; x != null; x = x.next) { 62 if (x.item == null) { 63 unlink(x); 64 return true; 65 } 66 } 67 } else { 68 //遍歷鏈表 69 for (Node<E> x = first; x != null; x = x.next) { 70 if (o.equals(x.item)) { 71 //找到了就刪除 72 unlink(x); 73 return true; 74 } 75 } 76 } 77 return false; 78 } 79 80 81 82 //改 83 public E set(int index, E element) { 84 85 //檢查下標是否合法 86 checkElementIndex(index); 87 88 //獲取指定下標的結點引用 89 Node<E> x = node(index); 90 91 //獲取指定下標結點的值 92 93 E oldVal = x.item; 94 95 //將結點元素設置為新的值 96 97 x.item = element; 98 99 //返回之前的值 100 101 return oldVal; 102 103 } 104 105 106 //查 107 public E get(int index) { 108 109 //檢查下標是否合法 110 checkElementIndex(index); 111 112 //返回指定下標的結點的值 113 return node(index).item; 114 }
LinkedList的添加元素的方法主要是調用LinkLast和LinkBefore兩個方法,LinkLast方法是在鏈表後面鏈接一個元素,LinkBefore方法是在鏈表中間插入一個元素。LinkedList的刪除方法通過調用unlink方法將某個元素從鏈表中移除。下麵是鏈表的插入和刪除操作的核心代碼。
1 //鏈接到指定結點之前 2 void linkBefore(E e, Node<E> succ) { 3 4 //獲取給定結點的上一個結點引用 5 final Node<E> pred = succ.prev; 6 7 //創建新結點, 新結點的上一個結點引用指向給定結點的上一個結點 8 //新結點的下一個結點的引用指向給定的結點 9 final Node<E> newNode = new Node<>(pred, e, succ); 10 11 //將給定結點的上一個結點引用指向新結點 12 succ.prev = newNode; 13 14 //如果給定結點的上一個結點為空, 表明給定結點為頭結點 15 if (pred == null) { 16 17 //將頭結點引用指向新結點 18 first = newNode; 19 } else { 20 //否則, 將給定結點的上一個結點的下一個結點引用指向新結點 21 pred.next = newNode; 22 } 23 24 //集合元素個數加一 25 size++; 26 27 //修改次數加一 28 modCount++; 29 30 } 31 32 33 34 //卸載指定結點 35 E unlink(Node<E> x) { 36 37 //獲取給定結點的元素 38 final E element = x.item; 39 40 //獲取給定結點的下一個結點的引用 41 final Node<E> next = x.next; 42 43 //獲取給定結點的上一個結點的引用 44 final Node<E> prev = x.prev; 45 46 //如果給定結點的上一個結點為空, 說明給定結點為頭結點 47 if (prev == null) { 48 49 //將頭結點引用指向給定結點的下一個結點 50 first = next; 51 } else { 52 //將上一個結點的後繼結點引用指向給定結點的後繼結點 53 prev.next = next; 54 //將給定結點的上一個結點置空 55 x.prev = null; 56 57 } 58 59 //如果給定結點的下一個結點為空, 說明給定結點為尾結點 60 if (next == null) { 61 62 //將尾結點引用指向給定結點的上一個結點 63 last = prev; 64 } else { 65 66 //將下一個結點的前繼結點引用指向給定結點的前繼結點 67 next.prev = prev; 68 x.next = null; 69 70 } 71 72 //將給定結點的元素置空 73 x.item = null; 74 75 //集合元素個數減一 76 size--; 77 //修改次數加一 78 modCount++; 79 return element; 80 81 }View Code
通過源碼的分析,對鏈表的插入和刪除的時間複雜度都是O(1),而對鏈表的查找和修改操作都需要遍歷鏈表進行元素的定位,這兩個操作都是調用的node(int index) 方法定位元素,如下代碼演示根據下標來定位元素。
1 //根據指定位置獲取結點 2 Node<E> node(int index) { 3 //如果下標在鏈表前半部分, 就從頭開始查起 4 if (index < (size >> 1)) { 5 Node<E> x = first; 6 for (int i = 0; i < index; i++) { 7 x = x.next; 8 } 9 return x; 10 } else { 11 //如果下標在鏈表後半部分, 就從尾開始查起 12 Node<E> x = last; 13 for (int i = size - 1; i > index; i--) { 14 x = x.prev; 15 } 16 return x; 17 } 18 }View Code
通過下標定位時先判斷是在鏈表的上半部還是下半部
上半部:從頭開始找;
下半部:從尾開始找;
因此通過下標的查找和修改操作的時間複雜度是O(n/2),通過對雙向鏈表的操作還可以實現單項隊列,雙向隊列和棧的功能。
單向隊列的操作的代碼:
1 //獲取頭結點 2 public E peek() { 3 final Node<E> f = first; 4 return (f == null) ? null : f.item; 5 } 6 7 //獲取頭結點 8 public E element() { 9 return getFirst(); 10 } 11 12 //彈出頭結點 13 public E poll() { 14 final Node<E> f = first; 15 return (f == null) ? null : unlinkFirst(f); 16 } 17 18 //移除頭結點 19 public E remove() { 20 return removeFirst(); 21 } 22 23 //在隊列尾部添加結點 24 public boolean offer(E e) { 25 return add(e); 26 }View Code
雙向隊列的操作:
1 //在頭部添加 2 public boolean offerFirst(E e) { 3 addFirst(e); 4 return true; 5 } 6 7 //在尾部添加 8 public boolean offerLast(E e) { 9 addLast(e); 10 return true; 11 } 12 13 //獲取頭結點 14 public E peekFirst() { 15 final Node<E> f = first; 16 return (f == null) ? null : f.item; 17 } 18 19 //獲取尾結點 20 public E peekLast() { 21 final Node<E> l = last; 22 return (l == null) ? null : l.item; 23 }View Code
棧操作:
1 //入棧 2 public void push(E e) { 3 addFirst(e); 4 } 5 6 //出棧 7 public E pop() { 8 return removeFirst(); 9 }View Code
對LindedList,有:
1. LinkedList是基於雙向鏈表實現的,不論是增刪改查方法還是隊列和棧的實現,都可通過操作結點實現
2. LinkedList無需提前指定容量,因為基於鏈表操作,集合的容量隨著元素的加入自動增加
3. LinkedList刪除元素後集合占用的記憶體自動縮小,無需像ArrayList一樣調用trimToSize()方法
4. LinkedList的所有方法沒有進行同步,因此它也不是線程安全的,應該避免在多線程環境下使用
5. 以上分析基於JDK1.7,其他版本會有些出入,因此不能一概而論
以上內容部分借鑒於:https://blog.csdn.net/iteen/article/details/83109717