若是說ArrayList是基於數組實現的List,那麼LinkedList是基於鏈表實現的List。java
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
能夠看獲得LinkedList繼承了AbstractSequentialList。實現了List,Deque. 後面兩個和ArrayList同樣,說明能夠被克隆和序列化。數組
而AbstractSequentialList基礎自AbstractList,並且還從新實現了get,set,add,remove,等等方法。數據結構
AbstractSequentialList的代碼以下:函數
package java.util; public abstract class AbstractSequentialList<E> extends AbstractList<E> { protected AbstractSequentialList() { } public E get(int index) { try { return listIterator(index).next(); } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); } } public E set(int index, E element) { try { ListIterator<E> e = listIterator(index); E oldVal = e.next(); e.set(element); return oldVal; } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); } } public void add(int index, E element) { try { listIterator(index).add(element); } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); } } public E remove(int index) { try { ListIterator<E> e = listIterator(index); E outCast = e.next(); e.remove(); return outCast; } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); } } public boolean addAll(int index, Collection<? extends E> c) { try { boolean modified = false; ListIterator<E> e1 = listIterator(index); Iterator<? extends E> e2 = c.iterator(); while (e2.hasNext()) { e1.add(e2.next()); modified = true; } return modified; } catch (NoSuchElementException exc) { throw new IndexOutOfBoundsException("Index: "+index); } } public Iterator<E> iterator() { return listIterator(); } public abstract ListIterator<E> listIterator(int index); }
而Dqueue接口 是一個雙向隊列,也就是既能夠先入先出,又能夠先入後出,再直白一點就是既能夠在頭部添加元素又在尾部添加元素,既能夠在頭部獲取元素又能夠在尾部獲取元素。看下Deque的定義:this
public interface Deque<E> extends Queue<E>
private transient Entry<E> header = new Entry<E>(null, null, null); private transient int size = 0;
size和ArrayList裏面的size同樣,記錄容器元素的個數。那這個Entry類型的變量header是個什麼鬼。spa
Entry是個內部類,來描述鏈表的節點的信息,代碼以下:.net
//描述鏈表節點的類 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; } }
能夠看得出ArrayList底層是採用雙向鏈表來實現的。指針
數據結構雙向鏈表是包含兩個指針的,pre指向前一個節點,next指向後一個節點,可是第一個節點head的pre指向null,最後一個節點的tail指向null。code
//默認構造 public LinkedList() { 鏈表的頭和尾都指向了本身 header.next = header.previous = header; } //將一個集合的元素來構造本身,這些元素按其 collection 的迭代器返回的順序排列 public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
從默認構造函數能夠看得出這是一個雙向循環鏈表,若是是雙向不循環鏈表的話,應該是:對象
header.next=header.previous=null。
有8個增長,5個增長是實現Dqueue裏面的添加函數,其他3個是實現List接口裏面的添加函數,而後實現Dqueue基本是調用實現List裏面的添加函數,因此咱們使用的時候直接調用List裏面的添加函數便可。能夠少壓一次棧。
//這5個add是LinkedList實現Dqueue裏面的添加函數,其實都是調用實現List接口裏面的函數 //將元素加到第一個,能夠看獲得,是加到header的後面,由於header就是一個空頭,裏面沒有存儲元素 public void addFirst(E e) { addBefore(e, header.next); } //將元素加列表的尾部 public void addLast(E e) { addBefore(e, header); } //封裝鏈表插入操做,分兩步走 //1.其實就是在構造的時候,本身的netx和previous指好 //2.本身的前節點的next指向本身,本身後結點的pre指向本身。 private Entry<E> addBefore(E e, Entry<E> entry) { Entry<E> newEntry = new Entry<E>(e, entry, entry.previous); //本身的前節點的next指向本身 newEntry.previous.next = newEntry; //本身後結點的pre指向本身 newEntry.next.previous = newEntry; //記數加1 size++; modCount++; return newEntry; } public boolean offer(E e) { return add(e); } public boolean offerFirst(E e) { addFirst(e); return true; } public boolean offerLast(E e) { addLast(e); return true; } //下面三個增長是實現List接口裏面的添加函數 //將元素加列表的尾部 public boolean add(E e) { addBefore(e, header); return true; } //添加一個集合元素到list中 public boolean addAll(Collection<? extends E> c) { //其實仍是調用在指定位置添加一個集合到list中 return addAll(size, c); } //在指定位置添加一個集合元素到list中 public boolean addAll(int index, Collection<? extends E> c) { //檢查下標是否越界 if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size); //將集合轉換爲數組 Object[] a = c.toArray(); //要增長元素的長度 int numNew = a.length; if (numNew==0) return false; modCount++; //找出要插入元素的先後節點 //找出其後節點,若是位置和size大小相等(爲何不是size-1,由於header佔了一位),則他的下一個節點是header //不然要查找index位置的節點,這個就是他的後一個節點 Entry<E> successor = (index==size ? header : entry(index)); //他的前一個節點,就是index位置的前一個節點。 Entry<E> predecessor = successor.previous; for (int i=0; i<numNew; i++) { Entry<E> e = new Entry<E>((E)a[i], successor, predecessor); //將它前一個節點的next指向本身 predecessor.next = e; //後面的元素插入到這個元素的後面 predecessor = e; } //將index位置的節點的前指針指向本身這樣就完成了鏈表的操做。 successor.previous = predecessor; size += numNew; return true; }
雙向鏈表的增長比起數組的增長稍微是要麻煩的理解一點,本身畫圖應該不難理解,總結起來,就是一句話主要就是插入會改變前節點的next和後一節點的pre,主要把前一節點的next指向本身,後一節點的pre指向本身,便可。
//刪除第一個容器元素 public E removeFirst() { return remove(header.next); } //刪除最後一個容器元素 public E removeLast() { return remove(header.previous); } //刪除指定的容器元素 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 { //循環遍歷節點,而後找到節點,而後刪除。時間複雜度O(n) for (Entry<E> e = header.next; e != header; e = e.next) { if (o.equals(e.element)) { remove(e); return true; } } } return false; } //刪除指定位置的容器元素 public E remove(int index) { //entry(index)是找到這個節點 return remove(entry(index)); } //刪除指定的節點,供本身調用 private E remove(Entry<E> e) { if (e == header) throw new NoSuchElementException(); E result = e.element; //將本身的前一節點的後指針執行本身的後一節點 e.previous.next = e.next; //講本身後一節點的前指針指向本身的前節點 //講本身置爲null,給gc回收 e.next.previous = e.previous; e.next = e.previous = null; e.element = null; //大小減一 size--; modCount++; return result; }
刪除則要簡單一點,刪除了本身以後,主要
將本身的前一節點的後指針執行本身後一節點 講本身後一節點的前指針指向本身的前節點
並且能夠看到remove(Object o)時間負責度爲O(n),而remove(int)的時間複雜度度爲O(n/2),由於裏面用到了二分查找的辦法。因此刪除的時候要注意了,要選用正確的方法刪除。(其實我轉載了一篇博客專門介紹這個LinkenList的侷限。)因此之前所說的增刪快的刪有時也是很慢的。
public E set(int index, E element) { Entry<E> e = entry(index); E oldVal = e.element; e.element = element; return oldVal; }
這個比較簡單,查找到,而後修改便可
咱們知道鏈表和數組相比,查找比數組要慢的很是多,數組直接定位,而鏈表每次咱們只能拿到一個頭部,因此無論找什麼,咱們都要從頭開始遍歷起,而LinkedList使用了雙向循環鏈表,這樣遍歷起來就會快不少,既能夠從頭日後找,又能夠從後往前找。直到找到index位置。
//查找指定index的鏈表裏面元素 public E get(int index) { return entry(index).element; } //這個方法很重要,基本上查找都是使用這個方法來進行查找。 //這兒就顯示雙向循環鏈表的好處,既能夠從頭日後遍歷,又能夠從後往前遍歷 //這兒使用了二分查找的方法,效率要高不少 private Entry<E> entry(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size); Entry<E> e = header; //若是下標小於size的通常,就從頭日後遍歷,找到元素 if (index < (size >> 1)) { for (int i = 0; i <= index; i++) e = e.next; } else {不然從後往前遍歷 for (int i = size; i > index; i--) e = e.previous; } return e; }
到這裏咱們明白,基於雙向循環鏈表實現的LinkedList,經過索引Index的操做時低效的,index所對應的元素越靠近中間所費時間越長。而向鏈表兩端插入和刪除元素則是很是高效的(若是不是兩端的話,都須要對鏈表進行遍歷查找)。
public boolean contains(Object o) { return indexOf(o) != -1; } 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; } 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; }
要遍歷,低效。
打完收工。