LinkedList由雙向鏈表實現,實現了List、Deque、Serializable、Cloneable介面,能被複製和執行個體化。
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
1.儲存結構Node
包含了三個欄位,item為資料項目,next為當前節點的下一個節點,prev為當前節點的前一個節點。
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; } }
2.欄位
成員變數也比較簡單,包含了一個頭結點和尾節點,size是鏈表中節點的個數。
/** * 鏈表中節點的個數 */ transient int size = 0; /** * 頭結點 */ transient Node<E> first; /** * 尾節點 */ transient Node<E> last;
3.建構函式
/** * 建構函式,什麼也不做 */ public LinkedList() { } /** * 建立一個包含集合中元素的鏈表 */ public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
4.添加方法
添加節點可以使用頭插法也可以使用尾插法,預設使用尾插法添加新節點。
/** * 在頭結點添加節點 */ private void linkFirst(E e) { //記錄當前的前端節點 final Node<E> f = first; //建立一個新節點,next指向前端節點,prev為null final Node<E> newNode = new Node<>(null, e, f); //將newNode設為前端節點 first = newNode; //如果原來的頭結點為空白 if (f == null) //將尾節點設為newNode,由於只有一個節點,newNode既是頭結點又是尾節點 last = newNode; else //將原來的頭結點的prev指向新建立的節點 f.prev = newNode; //更新大小 size++; modCount++; } /** * 從尾部添加節點 */ void linkLast(E e) { //先儲存尾節點 final Node<E> l = last; //建立一個prev指向尾節點,next指向空的節點 final Node<E> newNode = new Node<>(l, e, null); //將新節點設為尾節點 last = newNode; //如果原來的鏈表是空鏈表 if (l == null) //新節點也是頭結點 first = newNode; else //將原來尾節點的next指向新節點 l.next = newNode; size++; modCount++; } /** * 在succ之前插入非空節點 */ void linkBefore(E e, Node<E> succ) { // 擷取succ的前一個節點 final Node<E> pred = succ.prev; //建立新節點,prev指向succ的前一個節點,next指向succ final Node<E> newNode = new Node<>(pred, e, succ); //將succ的prev執行新建立的節點 succ.prev = newNode; //如果succ是頭結點 if (pred == null) //將新建立的節點設為頭結點 first = newNode; else //更新pred的next,將它指向新建立的節點 pred.next = newNode; size++; modCount++; } /** * 在首部添加節點 */ public void addFirst(E e) { linkFirst(e); } /** * 在尾部添加節點 */ public void addLast(E e) { linkLast(e); }/** * 添加節點,預設在尾部添加 */ public boolean add(E e) { linkLast(e); return true; } /** * 將集合中的元素添加到鏈表 */ public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } /** * 在指定的位置,將集合中的元素添加到鏈表 */ public boolean addAll(int index, Collection<? extends E> c) { //檢查位置是否合法 checkPositionIndex(index); //將集合轉為數組 Object[] a = c.toArray(); //集合的大小 int numNew = a.length; if (numNew == 0) return false; //pred要插入位置的前一個節點,succ為需要插入節點的位置 Node<E> pred, succ; //如果要插入位置等於鏈表的大小,說明需要在尾節點後面添加 if (index == size) { succ = null; //pred指向要插入位置的前一個節點也就是尾節點 pred = last; } else {//如果不在尾部添加 //尋找index位置的節點 succ = node(index); //index位置的前一個節點 pred = succ.prev; } //遍曆數組 for (Object o : a) { // @SuppressWarnings("unchecked") E e = (E) o; //建立新節點,prev指向pred Node<E> newNode = new Node<>(pred, e, null); // 如果要插入位置的前一個節點是空,說明要插入位置是頭結點 if (pred == null) first = newNode; else //將要插入位置的前一個節點的next指向新建立的節點 pred.next = newNode; //將pred指向當前節點 pred = newNode; } //如果要插入位置為null if (succ == null) { //將新插入的最後一個節點設為尾節點 last = pred; } else { //將新插入的最後一個節點的next指向原鏈表要插入位置的節點 pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true; } /** * 在指定位置添加節點 */ public void add(int index, E element) { checkPositionIndex(index); if (index == size) //在尾部添加 linkLast(element); else //找到index處的節點,然後在該節點前添加新節點 linkBefore(element, node(index)); }
5.擷取和尋找
(1)LinkedList提供了正向尋找和反向尋找兩種方法,需要對整個鏈表進行遍曆,如果找到返回對象在鏈表中的位置。
(2)如果根據索引尋找,LinkedList會根據鏈表中節點的個數/2算出中間位置的索引,然後比較,如果大於中間位置的索引,從中間索引之後開始尋找,否則反之。
/** * 擷取頭結點 */ public E getFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return f.item; } /** * 擷取尾節點 */ public E getLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return l.item; }/** * 是否包含對象o */ public boolean contains(Object o) { return indexOf(o) != -1; } /** * 根據位置擷取節點 */ public E get(int index) { //檢查位置是否合法 checkElementIndex(index); return node(index).item; } /** * 根據位置找到相應的節點 */ Node<E> node(int index) { // assert isElementIndex(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; } } // Search Operations /** * 正向尋找節點,如果找到返回節點的位置 */ public int indexOf(Object o) { int index = 0; if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; } /** * 反向尋找節點 */ public int lastIndexOf(Object o) { int index = size; if (o == null) { for (Node<E> x = last; x != null; x = x.prev) { index--; if (x.item == null) return index; } } else { for (Node<E> x = last; x != null; x = x.prev) { index--; if (o.equals(x.item)) return index; } } return -1; }
6.刪除
(1)從remove方法中可以看出如果刪除的節點是null的話是單獨進行處理的,LinkedList是允許null值插入的。
(2)刪除節點時同樣需要遍曆鏈表,尋找要刪除的節點,然後刪除。
/** * 刪除節點 */ public boolean remove(Object o) { //如果o為空白 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; } /** * 清空鏈表 */ public void clear() { //遍曆節點 for (Node<E> x = first; x != null; ) { //刪除每一個節點 Node<E> next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } first = last = null; size = 0; modCount++; } /** * 刪除節點 */ public E remove(int index) { checkElementIndex(index); return unlink(node(index)); }
7.更新
/** * 更新節點的值 */ public E set(int index, E element) { checkElementIndex(index); //根據位置找到該節點 Node<E> x = node(index); E oldVal = x.item; //更新節點的值 x.item = element; return oldVal; }
8.Queue操作和Dequeue操作
由於LinkedList實現了Queue介面,可以使用LinkedList類比隊列和雙端隊列,因此提供了一些隊列的操作方法。
/** * 返回頭結點的資料,如果此隊列為空白,則返回 null */ public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } /** * 擷取但不移除此隊列的頭;如果此隊列為空白,則拋出NoSuchElementException異常 */ public E element() { return getFirst(); } /** * 擷取並移除此隊列的頭,如果此隊列為空白,則返回 null * @return */ public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } /** * 擷取並移除此隊列的頭,如果此隊列為空白,則拋出NoSuchElementException異常 * @return */ public E remove() { return removeFirst(); } /** * 將指定的元素插入隊列,從尾部插入 * @param e * @return */ public boolean offer(E e) { return add(e); }
雙端隊列操作:
/** * 將指定的元素插入隊列,從頭部插入 * @param e * @return */ public boolean offerFirst(E e) { addFirst(e); return true; } /** * 將指定的元素插入此雙端隊列的末尾 */ public boolean offerLast(E e) { addLast(e); return true; } /** * 擷取但不移除此雙端隊列的第一個節點;如果此雙端隊列為空白,則返回 null */ public E peekFirst() { final Node<E> f = first; return (f == null) ? null : f.item; } /** * 擷取但不移除雙端隊列的最後一個節點,如果為空白返回null */ public E peekLast() { final Node<E> l = last; return (l == null) ? null : l.item; } /** * 擷取並移除隊列的第一個節點 * @since 1.6 */ public E pollFirst() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } /** * 擷取並移除隊列的最後一個節點 */ public E pollLast() { final Node<E> l = last; return (l == null) ? null : unlinkLast(l); } /** * 隊列中添加節點,從頭部添加 */ public void push(E e) { addFirst(e); } /** * 彈出/刪除節點,從頭部刪除 */ public E pop() { return removeFirst(); } /** * 從此雙端隊列移除第一次出現的指定元素,如果列表中不包含次元素,則沒有任何改變 */ public boolean removeFirstOccurrence(Object o) { return remove(o); } /** * 從此雙端隊列移除最後一次出現的指定元素,如果列表中不包含次元素,則沒有任何改變 */ public boolean removeLastOccurrence(Object o) { if (o == null) { for (Node<E> x = last; x != null; x = x.prev) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = last; x != null; x = x.prev) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; }
9.其他方法
private boolean isElementIndex(int index) { return index >= 0 && index < size; } /** * 檢驗位置是否合法(大於等於0小於等於節點的個數) */ private boolean isPositionIndex(int index) { return index >= 0 && index <= size; } /** * 錯誤資訊 */ private String outOfBoundsMsg(int index) { return "Index: "+index+", Size: "+size; } /** * 檢查索引是否合法 * @param index */ private void checkElementIndex(int index) { if (!isElementIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * 檢查位置是否合法 * @param index */ private void checkPositionIndex(int index) { if (!isPositionIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * 鏈表的大小 */ public int size() { return size; } /** * 複製方法 */ public Object clone() { LinkedList<E> clone = superClone(); // Put clone into "virgin" state clone.first = clone.last = null; clone.size = 0; clone.modCount = 0; // Initialize clone with our elements for (Node<E> x = first; x != null; x = x.next) clone.add(x.item); return clone; } /** * 轉為數組 */ public Object[] toArray() { //建立數組 Object[] result = new Object[size]; int i = 0; //遍曆鏈表 for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; return result; } /** * 轉為數組 */ @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length < size) a = (T[])java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), size); int i = 0; Object[] result = a; for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; if (a.length > size) a[size] = null; return a; } private static final long serialVersionUID = 876323262645176354L; /** * 序列化 */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out any hidden serialization magic s.defaultWriteObject(); // Write out size s.writeInt(size); // Write out all elements in the proper order. for (Node<E> x = first; x != null; x = x.next) s.writeObject(x.item); } /** * 還原序列化 */ @SuppressWarnings("unchecked") private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in any hidden serialization magic s.defaultReadObject(); // Read in size int size = s.readInt(); // Read in all elements in the proper order. for (int i = 0; i < size; i++) linkLast((E)s.readObject()); }
總結:
(1)LinkedList基於雙向鏈表實現,插入刪除效率高,同時也可以作為棧、隊列、雙端隊列來使用。
(2)LinkedList是非安全執行緒的。
(3)LinkedList中的元素可以為null。
(4)實現了Serializable、Cloneable介面,能被複製和執行個體化。
參考: jdk1.8.0_45源碼解讀——LinkedList的實現
【Java集合源碼剖析】LinkedList源碼剖析