白玉 IT哈哈
LinkedList也和ArrayList同樣實現了List接口,可是它執行插入和刪除操做時比ArrayList更加高效,由於它是基於鏈表的。基於鏈表也決定了它在隨機訪問方面要比ArrayList遜色一點。java
除此以外,LinkedList還提供了一些可使其做爲棧、隊列、雙端隊列的方法。這些方法中有些彼此之間只是名稱的區別,以使得這些名字在特定的上下文中顯得更加的合適。數組
先看LinkedList類的定義。ide
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList繼承自AbstractSequenceList、實現了List及Deque接口。其實AbstractSequenceList已經實現了List接口,這裏標註出List只是更加清晰而已。AbstractSequenceList提供了List接口骨幹性的實現以減小實現List接口的複雜度。Deque接口定義了雙端隊列的操做。this
LinkedList中之定義了兩個屬性:3d
private transient Entry<E> header = new Entry<E>(null, null, null); private transient int size = 0;
size確定就是LinkedList對象裏面存儲的元素個數了。LinkedList既然是基於鏈表實現的,那麼這個header確定就是鏈表的頭結點了,Entry就是節點對象了。一下是Entry類的代碼。指針
private static class Entry<E> { E element; Entry<E> next; Entry<E> previous; Entry(E element, Entry<E> next, Entry<E> previous) { this.element = element; this.next = next; this.previous = previous; } }
只定義了存儲的元素、前一個元素、後一個元素,這就是雙向鏈表的節點的定義,每一個節點只知道本身的前一個節點和後一個節點。
來看LinkedList的構造方法。code
public LinkedList() { header.next = header.previous = header; } public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
LinkedList提供了兩個構造方法。第一個構造方法不接受參數,只是將header節點的前一節點和後一節點都設置爲自身(注意,這個是一個雙向循環鏈表,若是不是循環鏈表,空鏈表的狀況應該是header節點的前一節點和後一節點均爲null),這樣整個鏈表其實就只有header一個節點,用於表示一個空的鏈表。第二個構造方法接收一個Collection參數c,調用第一個構造方法構造一個空的鏈表,以後經過addAll將c中的元素所有添加到鏈表中。來看addAll的內容。對象
public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } // index參數指定collection中插入的第一個元素的位置 public boolean addAll(int index, Collection<? extends E> c) { // 插入位置超過了鏈表的長度或小於0,報IndexOutOfBoundsException異常 if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size); Object[] a = c.toArray(); int numNew = a.length; // 若須要插入的節點個數爲0則返回false,表示沒有插入元素 if (numNew==0) return false; modCount++; // 保存index處的節點。插入位置若是是size,則在頭結點前面插入,不然獲取index處的節點 Entry<E> successor = (index==size ? header : entry(index)); // 獲取前一個節點,插入時須要修改這個節點的next引用 Entry<E> predecessor = successor.previous; // 按順序將a數組中的第一個元素插入到index處,將以後的元素插在這個元素後面 for (int i=0; i<numNew; i++) { // 結合Entry的構造方法,這條語句是插入操做,至關於C語言中鏈表中插入節點並修改指針 Entry<E> e = new Entry<E>((E)a[i], successor, predecessor); // 插入節點後將前一節點的next指向當前節點,至關於修改前一節點的next指針 predecessor.next = e; // 至關於C語言中成功插入元素後將指針向後移動一個位置以實現循環的功能 predecessor = e; } // 插入元素前index處的元素連接到插入的Collection的最後一個節點 successor.previous = predecessor; // 修改size size += numNew; return true; }
構造方法中的調用了addAll(Collection<? extends E> c)方法,而在addAll(Collection<? extends E> c)方法中僅僅是將size當作index參數調用了addAll(int index,Collection<? extends E> c)方法。blog
private Entry<E> entry(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size); Entry<E> e = header; // 根據這個判斷決定從哪一個方向遍歷這個鏈表 if (index < (size >> 1)) { for (int i = 0; i <= index; i++) e = e.next; } else { // 能夠經過header節點向前遍歷,說明這個一個循環雙向鏈表,header的previous指向鏈表的最後一個節點,這也驗證了構造方法中對於header節點的先後節點均指向本身的解釋 for (int i = size; i > index; i--) e = e.previous; } return e; }
結合上面代碼中的註釋及雙向循環鏈表的知識,應該很容易理解LinkedList構造方法所涉及的內容。下面開始分析LinkedList的其餘方法。繼承
add(E e)
public boolean add(E e) { addBefore(e, header); return true; }
從上面的代碼能夠看出,add(E e)方法只是調用了addBefore(E e,Entry<E> entry)方法,而且返回true。
addBefore(E e,Entry<E> entry)
private Entry<E> addBefore(E e, Entry<E> entry) { Entry<E> newEntry = new Entry<E>(e, entry, entry.previous); newEntry.previous.next = newEntry; newEntry.next.previous = newEntry; size++; modCount++; return newEntry; }
addBefore(E e,Entry<E> entry)方法是個私有方法,因此沒法在外部程序中調用(固然,這是通常狀況,你能夠經過反射上面的仍是能調用到的)。
addBefore(E e,Entry<E> entry)先經過Entry的構造方法建立e的節點newEntry(包含了將其下一個節點設置爲entry,上一個節點設置爲entry.previous的操做,至關於修改newEntry的「指針」),以後修改插入位置後newEntry的前一節點的next引用和後一節點的previous引用,使鏈表節點間的引用關係保持正確。以後修改和size大小和記錄modCount,而後返回新插入的節點。
總結,addBefore(E e,Entry<E> entry)實如今entry以前插入由e構造的新節點。而add(E e)實如今header節點以前插入由e構造的新節點。
public void add(int index, E element) { addBefore(element, (index==size ? header : entry(index))); }
也是調用了addBefore(E e,Entry<E> entry)方法,只是entry節點由index的值決定。
構造方法,addAll(Collection<? extends E> c),add(E e),addBefor(E e,Entry<E> entry)方法能夠構造鏈表並在指定位置插入節點,爲了便於理解,下面給出插入節點的示意圖。
addFirst(E e)
public void addFirst(E e) { addBefore(e, header.next); }
addLast(E e)
public void addLast(E e) { addBefore(e, header); }
看上面的示意圖,結合addBefore(E e,Entry<E> entry)方法,很容易理解addFrist(E e)只需實如今header元素的下一個元素以前插入,即示意圖中的一號以前。addLast(E e)只需在實如今header節點前(由於是循環鏈表,因此header的前一個節點就是鏈表的最後一個節點)插入節點(插入後在2號節點以後)。
clear()
public void clear() { Entry<E> e = header.next; // e能夠理解爲一個移動的「指針」,由於是循環鏈表,因此回到header的時候說明已經沒有節點了 while (e != header) { // 保留e的下一個節點的引用 Entry<E> next = e.next; // 接觸節點e對先後節點的引用 e.next = e.previous = null; // 將節點e的內容置空 e.element = null; // 將e移動到下一個節點 e = next; } // 將header構形成一個循環鏈表,同構造方法構造一個空的LinkedList header.next = header.previous = header; // 修改size size = 0; modCount++; }
上面代碼中的註釋已經足以解釋這段代碼的邏輯,須要注意的是提到的「指針」僅僅是概念上的類比,Java並不存在「指針」的概念,而只有引用,爲了便於理解因此部分說明使用了「指針」。
contains(Object o)
public boolean contains(Object o) { return indexOf(o) != -1; }
僅僅只是判斷o在鏈表中的索引。先看indexOf(Object o)方法。
public int indexOf(Object o) { int index = 0; if (o==null) { for (Entry e = header.next; e != header; e = e.next) { if (e.element==null) return index; index++; } } else { for (Entry e = header.next; e != header; e = e.next) { if (o.equals(e.element)) return index; index++; } } return -1; }
indexOf(Object o)判斷o鏈表中是否存在節點的element和o相等,若相等則返回該節點在鏈表中的索引位置,若不存在則放回-1。
contains(Object o)方法經過判斷indexOf(Object o)方法返回的值是不是-1來判斷鏈表中是否包含對象o。
element()
public E element() { return getFirst(); }
getFirst()
public E getFirst() { if (size==0) throw new NoSuchElementException(); return header.next.element; }
element()方法調用了getFirst()返回鏈表的第一個節點的元素。爲何要提供功能同樣的兩個方法,像是包裝了一下名字?其實這只是爲了在不一樣的上下文「語境」中能經過更貼切的方法名調用罷了。
get(int index)
public E get(int index) { return entry(index).element; }
get(int index)方法用於得到指定索引位置的節點的元素。它經過entry(int index)方法獲取節點。entry(int index)方法遍歷鏈表並獲取節點,在上面有說明過,再也不陳述。
set(int index,E element)
public E set(int index, E element) {
Entry<E> e = entry(index);
E oldVal = e.element;
e.element = element;
return oldVal;
}
先獲取指定索引的節點,以後保留原來的元素,而後用element進行替換,以後返回原來的元素。
getLast()
public E getLast() { if (size==0) throw new NoSuchElementException(); return header.previous.element; }
getLast()方法和getFirst()方法相似,只是獲取的是header節點的前一個節點的元素。由於是循環鏈表,因此header節點的前一節點就是鏈表的最後一個節點。
lastIndexOf(Object o)
public int lastIndexOf(Object o) { int index = size; if (o==null) { for (Entry e = header.previous; e != header; e = e.previous) { index--; if (e.element==null) return index; } } else { for (Entry e = header.previous; e != header; e = e.previous) { index--; if (o.equals(e.element)) return index; } } return -1;
}
由於查找的是last index,即最後一次出現的位置,因此採用由後向前的遍歷方式。由於採用了有後向前的遍歷,因此index被賦值爲size,而且循環體內執行時都進行減操做。分兩種狀況判斷是否存在,分別是null和不爲空。
offer(E e)
public boolean offer(E e) { return add(e); }
在鏈表尾部插入元素。
offerFirst(E e)
public boolean offerFirst(E e) { addFirst(e); return true; }
在鏈表開頭插入元素。
offerLast(E e)
public boolean offerLast(E e) { addLast(e); return true; }
在鏈表末尾插入元素。
上面這三個方法都只是調用了相應的add方法,一樣只是提供了不一樣的方法名在不一樣的語境下使用。
peek()
public E peek() { if (size==0) return null; return getFirst(); }
peekFirst()
public E peekFirst() { if (size==0) return null; return getFirst(); }
peekLast()
public E peekLast() { if (size==0) return null; return getLast(); }
上面的三個方法也很簡單,只是調用了對應的get方法。
poll()
public E poll() { if (size==0) return null; return removeFirst(); }
pollFirst()
public E pollFirst() { if (size==0) return null; return removeFirst(); }
pollLast()
public E pollLast() { if (size==0) return null; return removeLast(); }
poll相關的方法都是獲取並移除某個元素。都是和remove操做相關。
pop()
public E pop() { return removeFirst(); }
push(E e)
public void push(E e) { addFirst(e); }
這兩個方法對應棧的操做,即彈出一個元素和壓入一個元素,僅僅是調用了removeFirst()和addFirst()方法。
下面集中看remove相關操做的方法。
remove()
public E remove() { return removeFirst(); }
**remove(int index)** public E remove(int index) { return remove(entry(index)); } **remove(Object o)** public boolean remove(Object o) { if (o==null) { for (Entry<E> e = header.next; e != header; e = e.next) { if (e.element==null) { remove(e); return true; } } } else { for (Entry<E> e = header.next; e != header; e = e.next) { if (o.equals(e.element)) { remove(e); return true; } } } return false; } **removeFirst()** public E removeFirst() { return remove(header.next); } **removeLast()** public E removeLast() { return remove(header.previous); } **removeFirstOccurrence()** public boolean removeFirstOccurrence(Object o) { return remove(o); } **removeLastOccurence()** public boolean removeLastOccurrence(Object o) { if (o==null) { for (Entry<E> e = header.previous; e != header; e = e.previous) { if (e.element==null) { remove(e); return true; } } } else { for (Entry<E> e = header.previous; e != header; e = e.previous) { if (o.equals(e.element)) { remove(e); return true; } } } return false; }
幾個remove方法最終都是調用了一個私有方法:remove(Entry<E> e),只是其餘簡單邏輯上的區別。下面分析remove(Entry<E> e)方法。
private E remove(Entry<E> e) { if (e == header) throw new NoSuchElementException(); // 保留將被移除的節點e的內容 E result = e.element; // 將前一節點的next引用賦值爲e的下一節點 e.previous.next = e.next; // 將e的下一節點的previous賦值爲e的上一節點 e.next.previous = e.previous; // 上面兩條語句的執行已經致使了沒法在鏈表中訪問到e節點,而下面解除了e節點對先後節點的引用 e.next = e.previous = null; // 將被移除的節點的內容設爲null e.element = null; // 修改size大小 size--; modCount++; // 返回移除節點e的內容 return result; }
clone()
public Object clone() { LinkedList<E> clone = null; try { clone = (LinkedList<E>) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } clone.header = new Entry<E>(null, null, null); clone.header.next = clone.header.previous = clone.header; clone.size = 0; clone.modCount = 0; for (Entry<E> e = header.next; e != header; e = e.next) clone.add(e.element); return clone; }
調用父類的clone()方法初始化對象鏈表clone,將clone構形成一個空的雙向循環鏈表,以後將header的下一個節點開始將逐個節點添加到clone中。最後返回克隆的clone對象。
public Object[] toArray() { Object[] result = new Object[size]; int i = 0; for (Entry<E> e = header.next; e != header; e = e.next) result[i++] = e.element; return result; }
建立大小和LinkedList相等的數組result,遍歷鏈表,將每一個節點的元素element複製到數組中,返回數組。
先判斷出入的數組a的大小是否足夠,若大小不夠則拓展。這裏用到了發射的方法,從新實例化了一個大小爲size的數組。以後將數組a賦值給數組result,遍歷鏈表向result中添加的元素。最後判斷數組a的長度是否大於size,若大於則將size位置的內容設置爲null。返回a。
從代碼中能夠看出,數組a的length小於等於size時,a中全部元素被覆蓋,被拓展來的空間存儲的內容都是null;若數組a的length的length大於size,則0至size-1位置的內容被覆蓋,size位置的元素被設置爲null,size以後的元素不變。
爲何不直接對數組a進行操做,要將a賦值給result數組以後對result數組進行操做?
LinkedList的Iterator
除了Entry,LinkedList還有一個內部類:ListItr。
ListItr實現了ListIterator接口,可知它是一個迭代器,經過它能夠遍歷修改LinkedList。
在LinkedList中提供了獲取ListItr對象的方法:listIterator(int index)。
public ListIterator<E> listIterator(int index) { return new ListItr(index); }
該方法只是簡單的返回了一個ListItr對象。
LinkedList中還有經過集成得到的listIterator()方法,該方法只是調用了listIterator(int index)而且傳入0。
下面詳細分析ListItr。
下面是一個ListItr的使用實例。
結果:
LinkedList還有一個提供Iterator的方法:descendingIterator()。該方法返回一個DescendingIterator對象。DescendingIterator是LinkedList的一個內部類。
public Iterator<E> descendingIterator() { return new DescendingIterator(); }
下面分析詳細分析DescendingIterator類。
從類名和上面的代碼能夠看出這是一個反向的Iterator,代碼很簡單,都是調用的ListItr類中的方法。