此文已由做者趙計剛受權網易雲社區發佈。算法
歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗。數組
1、對於LinkedList須要掌握的八點內容安全
LinkedList的建立:即構造器服務器
往LinkedList中添加對象:即add(E)方法源碼分析
獲取LinkedList中的單個對象:即get(int index)方法this
修改LinkedList中的指定索引的節點的數據set(int index, E element)spa
刪除LinkedList中的對象:即remove(E),remove(int index)方法.net
遍歷LinkedList中的對象:即iterator,在實際中更經常使用的是加強型的for循環去作遍歷線程
判斷對象是否存在於LinkedList中:contain(E)指針
LinkedList中對象的排序:主要取決於所採起的排序算法(之後講)
2、源碼分析
2.一、LinkedList的建立
實現方式:
List<String> strList0 = new LinkedList<String>();
源代碼:在讀源代碼以前,首先要知道什麼是環形雙向鏈表,參考《算法導論(第二版)》P207
private transient Entry<E> header = new Entry<E>(null, null, null);//底層是雙向鏈表,這時先初始化一個空的header節點 private transient int size = 0;//鏈表中的所存儲的元素個數 /** * 構造環形雙向鏈表 */ public LinkedList() { header.next = header.previous = header;//造成環形雙向鏈表 }
Entry是LinkedList的一個內部類:
/** * 鏈表節點 */ 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; } }
執行完上述的無參構造器後:造成的空環形雙向鏈表以下:
其中,左上角爲previous,右下角爲next
2.二、往LinkedList中添加對象(add(E e))
實現方式:
strList0.add("hello");
源代碼:
/** * 在鏈表尾部增長新節點,新節點封裝的數據爲e */ public boolean add(E e) { addBefore(e, header);//在鏈表尾部增長新節點,新節點封裝的數據爲e return true; }
/* * 在鏈表指定節點entry後增長新節點,新節點封裝的數據爲e */ 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++; //鏈表中元素個數+1 modCount++; //與ArrayList相同,用於在遍歷時查看是否發生了add和remove操做 return newEntry; }
在添加一個元素後的新環形雙向鏈表以下:
在上述的基礎上,再調用一次add(E)後,新的環形雙向鏈表以下:
這裏,結合着代碼註釋與圖片去看add(E)的源代碼就好。
注意:在添加元素方面LinkedList不須要考慮數組擴容和數組複製,只須要新建一個對象,可是須要修改先後兩個對象的屬性。
2.三、獲取LinkedList中的單個對象(get(int index))
實現方式:
strList.get(0);//注意:下標從0開始
源代碼:
/** * 返回索引值爲index節點的數據,index從0開始計算 */ public E get(int index) { return entry(index).element; }
/** * 獲取指定index索引位置的節點(須要遍歷鏈表) */ private Entry<E> entry(int index) { //index:0~size-1 if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Index:"+index+", Size:"+size); Entry<E> e = header;//頭節點:既做爲頭節點也做爲尾節點 if (index < (size >> 1)) {//index<size/2,則說明index在前半個鏈表中,從前日後找 for (int i = 0; i <= index; i++) e = e.next; } else {//index>=size/2,則說明index在後半個鏈表中,從後往前找 for (int i = size; i > index; i--) e = e.previous; } return e; }
注意:
鏈表節點的按索引查找,須要遍歷鏈表;而數組不須要。
header節點既是頭節點也是尾節點
雙向鏈表的查找,先去判斷索引值index是否小於size/2,若小於,從header節點開始,從前日後找;若大於等於,從header節點開始,從後往前找
size>>1,右移一位等於除以2;左移一位等於乘以2
2.四、修改LinkedList中指定索引的節點的數據:set(int index, E element)
使用方式:
strList.set(0, "world");
源代碼:
/** * 修改指定索引位置index上的節點的數據爲element */ public E set(int index, E element) { Entry<E> e = entry(index);//查找index位置的節點 E oldVal = e.element;//獲取該節點的舊值 e.element = element;//將新值賦給該節點的element屬性 return oldVal;//返回舊值 }
注意:entry(int index)查看上邊
2.五、刪除LinkedList中的對象
2.5.一、remove(Object o)
使用方式:
strList.remove("world")
源代碼:
/** * 刪除第一個出現的指定元數據爲o的節點 */ public boolean remove(Object o) { if (o == null) {//從前日後刪除第一個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; }
/* * 刪除節點e */ private E remove(Entry<E> e) { //header節點不可刪除 if (e == header) throw new NoSuchElementException(); E result = e.element; //調整要刪除節點的先後節點的指針指向 e.previous.next = e.next; e.next.previous = e.previous; //將要刪除元素的三個屬性置空 e.next = e.previous = null; e.element = null; size--;//size-1 modCount++; return result; }
注意:
header節點不可刪除
2.5.二、remove(int index)
使用方式:
strList.remove(0);
源代碼:
/** * 刪除指定索引的節點 */ public E remove(int index) { return remove(entry(index)); }
注意:
remove(entry(index))見上邊
remove(Object o)須要遍歷鏈表,remove(int index)也須要
2.六、判斷對象是否存在於LinkedList中(contains(E))
源代碼:
/** * 鏈表中是否包含指定數據o的節點 */ public boolean contains(Object o) { return indexOf(o) != -1; }
/** * 從header開始,查找第一個出現o的索引 */ public int indexOf(Object o) { int index = 0; if (o == null) {//從header開始,查找第一個出現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的索引
2.七、遍歷LinkedList中的對象(iterator())
使用方式:
List<String> strList = new LinkedList<String>(); strList.add("jigang"); strList.add("nana"); strList.add("nana2"); Iterator<String> it = strList.iterator(); while (it.hasNext()) { System.out.println(it.next()); }
源代碼:iterator()方法是在父類AbstractSequentialList中實現的,
public Iterator<E> iterator() { return listIterator(); }
listIterator()方法是在父類AbstractList中實現的,
public ListIterator<E> listIterator() { return listIterator(0); }
listIterator(int index)方法是在父類AbstractList中實現的,
public ListIterator<E> listIterator(final int index) { if (index < 0 || index > size()) throw new IndexOutOfBoundsException("Index: " + index); return new ListItr(index); }
該方法返回AbstractList的一個內部類ListItr對象
ListItr:
private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) { cursor = index; }
上邊這個類並不完整,它繼承了內部類Itr,還擴展了一些其餘方法(eg.向前查找方法hasPrevious()等),至於hasNext()/next()等方法仍是來自於Itr的。
Itr:
private class Itr implements Iterator<E> { int cursor = 0;//標記位:標記遍歷到哪個元素 int expectedModCount = modCount;//標記位:用於判斷是否在遍歷的過程當中,是否發生了add、remove操做 //檢測對象數組是否還有元素 public boolean hasNext() { return cursor != size();//若是cursor==size,說明已經遍歷完了,上一次遍歷的是最後一個元素 } //獲取元素 public E next() { checkForComodification();//檢測在遍歷的過程當中,是否發生了add、remove操做 try { E next = get(cursor++); return next; } catch (IndexOutOfBoundsException e) {//捕獲get(cursor++)方法的IndexOutOfBoundsException checkForComodification(); throw new NoSuchElementException(); } } //檢測在遍歷的過程當中,是否發生了add、remove等操做 final void checkForComodification() { if (modCount != expectedModCount)//發生了add、remove操做,這個咱們能夠查看add等的源代碼,發現會出現modCount++ throw new ConcurrentModificationException(); } }
注:
上述的Itr我去掉了一個此時用不到的方法和屬性。
這裏的get(int index)方法參照2.3所示。
3、總結
LinkedList基於環形雙向鏈表方式實現,無容量的限制
添加元素時不用擴容(直接建立新節點,調整插入節點的先後節點的指針屬性的指向便可)
線程不安全
get(int index):須要遍歷鏈表
remove(Object o)須要遍歷鏈表
remove(int index)須要遍歷鏈表
contains(E)須要遍歷鏈表
免費領取驗證碼、內容安全、短信發送、直播點播體驗包及雲服務器等套餐
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】 使用QUIC