LinkedList是一個以雙向鏈表實現的List,它除了做爲List使用,還能夠做爲隊列或者棧來使用,它是怎麼實現的呢?讓咱們一塊兒來學習吧。node
經過繼承體系,咱們能夠看到LinkedList不只實現了List接口,還實現了Queue和Deque接口,因此它既能做爲List使用,也能做爲雙端隊列使用,固然也能夠做爲棧使用。源碼分析
// 元素個數 transient int size = 0; // 鏈表首節點 transient Node<E> first; // 鏈表尾節點 transient Node<E> last;
典型的雙鏈表結構學習
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; } }
public LinkedList() { } public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
是一個無界的隊列this
做爲一個雙端隊列,添加元素主要有兩種,一種是在隊列尾部添加元素,一種是在隊列首部添加元素,這兩種形式在LinkedList中主要是經過下面兩個方法來實現的。spa
// 從隊列首添加元素 private void linkFirst(E e) { // 首節點 final Node<E> f = first; // 建立新節點,新節點的next是首節點 final Node<E> newNode = new Node<>(null, e, f); // 讓新節點做爲新的首節點 first = newNode; // 判斷是否是第一個添加的元素 // 若是是就把last也置爲新節點 // 不然把原首節點的prev指針置爲新節點 if (f == null) last = newNode; else f.prev = newNode; // 元素個數加1 size++; // 修改次數加1,說明這是一個支持fail-fast的集合 modCount++; } // 從隊列尾添加元素 void linkLast(E e) { // 隊列尾節點 final Node<E> l = last; // 建立新節點,新節點的prev是尾節點 final Node<E> newNode = new Node<>(l, e, null); // 讓新節點成爲新的尾節點 last = newNode; // 判斷是否是第一個添加的元素 // 若是是就把first也置爲新節點 // 不然把原尾節點的next指針置爲新節點 if (l == null) first = newNode; else l.next = newNode; // 元素個數加1 size++; // 修改次數加1 modCount++; } public void addFirst(E e) { linkFirst(e); } public void addLast(E e) { linkLast(e); } // 做爲無界隊列,添加元素老是會成功的 public boolean offerFirst(E e) { addFirst(e); return true; } public boolean offerLast(E e) { addLast(e); return true; }
典型的雙鏈表在首尾添加元素的方法. 上面是做爲雙端隊列來看,它的添加元素分爲首尾添加元素.指針
做爲List,是要支持在中間添加元素的,主要是經過下面這個方法實現的。code
// 在節點succ以前添加元素 void linkBefore(E e, Node<E> succ) { // succ是待添加節點的後繼節點 // 找到待添加節點的前置節點 final Node<E> pred = succ.prev; // 在其前置節點和後繼節點之間建立一個新節點 final Node<E> newNode = new Node<>(pred, e, succ); // 修改後繼節點的前置指針指向新節點 succ.prev = newNode; // 判斷前置節點是否爲空 // 若是爲空,說明是第一個添加的元素,修改first指針 // 不然修改前置節點的next爲新節點 if (pred == null) first = newNode; else pred.next = newNode; // 修改元素個數 size++; // 修改次數加1 modCount++; } // 尋找index位置的節點 Node<E> node(int index) { // 由於是雙鏈表 // 因此根據index是在前半段仍是後半段決定從前遍歷仍是從後遍歷 // 這樣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; } } // 在指定index位置處添加元素 public void add(int index, E element) { // 判斷是否越界 checkPositionIndex(index); // 若是index是在隊列尾節點以後的一個位置 // 把新節點直接添加到尾節點以後 // 不然調用linkBefore()方法在中間添加節點 if (index == size) linkLast(element); else linkBefore(element, node(index)); }
在隊列首尾添加元素很高效,時間複雜度爲O(1)。
在中間添加元素比較低效,首先要先找到插入位置的節點,再修改先後節點的指針,時間複雜度爲O(n)。blog
做爲雙端隊列,刪除元素也有兩種方式,一種是隊列首刪除元素,一種是隊列尾刪除元素。
做爲List,又要支持中間刪除元素,因此刪除元素一個有三個方法,分別以下。繼承
// 刪除首節點 private E unlinkFirst(Node<E> f) { // 首節點的元素值 final E element = f.item; // 首節點的next指針 final Node<E> next = f.next; // 添加首節點的內容,協助GC f.item = null; f.next = null; // help GC // 把首節點的next做爲新的首節點 first = next; // 若是隻有一個元素,刪除了,把last也置爲空 // 不然把next的前置指針置爲空 if (next == null) last = null; else next.prev = null; // 元素個數減1 size--; // 修改次數加1 modCount++; // 返回刪除的元素 return element; } // 刪除尾節點 private E unlinkLast(Node<E> l) { // 尾節點的元素值 final E element = l.item; // 尾節點的前置指針 final Node<E> prev = l.prev; // 清空尾節點的內容,協助GC l.item = null; l.prev = null; // help GC // 讓前置節點成爲新的尾節點 last = prev; // 若是隻有一個元素,刪除了把first置爲空 // 不然把前置節點的next置爲空 if (prev == null) first = null; else prev.next = null; // 元素個數減1 size--; // 修改次數加1 modCount++; // 返回刪除的元素 return element; } // 刪除指定節點x E unlink(Node<E> x) { // x的元素值 final E element = x.item; // x的前置節點 final Node<E> next = x.next; // x的後置節點 final Node<E> prev = x.prev; // 若是前置節點爲空 // 說明是首節點,讓first指向x的後置節點 // 不然修改前置節點的next爲x的後置節點 if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } // 若是後置節點爲空 // 說明是尾節點,讓last指向x的前置節點 // 不然修改後置節點的prev爲x的前置節點 if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } // 清空x的元素值,協助GC x.item = null; // 元素個數減1 size--; // 修改次數加1 modCount++; // 返回刪除的元素 return element; } // remove的時候若是沒有元素拋出異常 public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } // remove的時候若是沒有元素拋出異常 public E removeLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l); } // poll的時候若是沒有元素返回null public E pollFirst() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } // poll的時候若是沒有元素返回null public E pollLast() { final Node<E> l = last; return (l == null) ? null : unlinkLast(l); } // 刪除中間節點 public E remove(int index) { // 檢查是否越界 checkElementIndex(index); // 刪除指定index位置的節點 return unlink(node(index)); }
在隊列首尾刪除元素很高效,時間複雜度爲O(1)。
在中間刪除元素比較低效,首先要找到刪除位置的節點,再修改先後指針,時間複雜度爲O(n)。接口
LinkedList是雙端隊列, 雙端隊列能夠做爲棧使用.
棧的特性是LIFO(Last In First Out),因此做爲棧使用也很簡單,添加刪除元素都只操做隊列首節點便可。
public void push(E e) { addFirst(e); } public E pop() { return removeFirst(); }