LinkedList

LinkedList

四個關注點在LinkedList上的答案c++

關 注 點 結 論
LinkedList是否容許空 容許
LinkedList是否容許重複數據 容許
LinkedList是否有序 有序
LinkedList是否線程安全 非線程安全

LinkedList是基於鏈表實現的,那什麼是鏈表呢?鏈表原先是c/c++的一個概念,是一種線性的存儲結構,意思是將存儲的數據存儲在單元格里面,與數組不一樣的是,它還帶一個存儲地址的單元格,值得注意的是 LinkedList雙向鏈表,那什麼是雙向鏈表呢?和單向鏈表不一樣的是,雙向鏈表在存儲數據的單元格的兩端各帶有一個存儲地址的單元格,爲何叫鏈表呢,故名思意,這個結構就像一個鏈子同樣是首尾相接的,能夠隨意的拆分,如圖:算法

private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;
...
}


中間的element真正存儲數據的單元格,頭和尾是用來存儲地址的引用值的編程

 

添加元素

先來一段代碼數組

1 public static void main(String[] args)
2 {
3     List<String> list = new LinkedList<String>();
4     list.add("111");
5     list.add("222");
6 }

 

看作了什麼:安全

一共五步,每一步的操做步驟都用數字表示出來了:數據結構

一、新的entry的element賦值爲111;dom

二、新的entry的next是header的next,header的next是0x00000000,因此新的entry的next即0x00000000;atom

三、新的entry的previous是header的previous,header的previous是0x00000000,因此新的entry的next即0x00000000;spa

四、"newEntry.previous.next = newEntry",首先是newEntry的previous,因爲newEntry的previous爲0x00000000,因此newEntry.previous表示的是header,header的next爲newEntry,即header的next爲0x00000001;線程

五、"newEntry.next.previous = newEntry",和4同樣,把header的previous設置爲0x00000001;

爲何要這麼作?還記得雙向鏈表的兩個特色嗎,一是任意節點均可以向前和向後尋址,二是整個鏈表頭的previous表示的是鏈表的尾Entry,鏈表尾的next表示的是鏈表的頭Entry。如今鏈表頭就是0x00000000這個Entry,鏈表尾就是0x00000001,能夠本身看圖觀察、思考一下是否符合這兩個條件。

最後看一下add了一個字符串"222"作了什麼,假設新new出來的Entry的地址是0x00000002,畫圖表示:

這徹底能夠解釋了問什麼雙向鏈表既能夠向前查找,有能夠向後查找

查看元素

先查一下LinkedList是怎麼寫的:

public E get(int index) {
   return entry(index).element;
}

1 private Entry<E> entry(int index) {
2     if (index < 0 || index >= size)
3         throw new IndexOutOfBoundsException("Index: "+index+
4                                             ", Size: "+size);
5     Entry<E> e = header;
   //索引大於一半後往前查找
6     if (index < (size >> 1)) {
7         for (int i = 0; i <= index; i++)
8             e = e.next;
9     } else {
    //不然順序查找
10         for (int i = size; i > index; i--)
11             e = e.previous;
12     }
13     return e;
14 }


 

這段代碼就體現出了雙向鏈表的好處了。雙向鏈表增長了一點點的空間消耗(每一個Entry裏面還要維護它的前置Entry的引用),同時也增長了必定的編程複雜度,卻大大提高了效率。

因爲LinkedList是雙向鏈表,因此LinkedList既能夠向前查找,也能夠向後查找,第6行~第12行的做用就是:當index小於數組大小的一半的時候(size >> 1表示size / 2,使用移位運算提高代碼運行效率),向後查找;不然,向前查找

這樣,在個人數據結構裏面有10000個元素,剛巧查找的又是第10000個元素的時候,就不須要從頭遍歷10000次了,向後遍歷便可,一次就能找到我要的元素。

 

刪除元素

先看源碼

1 public E remove(int index) {
2     return remove(entry(index));
3 }

1 private E remove(Entry<E> e) {
    //先找到位置
2 if (e == header)
3     throw new NoSuchElementException();
4 //在將它置空,利用回收機制回收
5         E result = e.element;
6 e.previous.next = e.next;
7 e.next.previous = e.previous;
8        e.next = e.previous = null;
9        e.element = null;
10 size--;
11 modCount++;
12        return result;
13 }

畫個圖利於理解:

這個問題我稍微擴展深刻一點:按照Java虛擬機HotSpot採用的垃圾回收檢測算法----根節點搜索算法來講,即便previous、element、next不設置爲null也是能夠回收這個Entry的,由於此時這個Entry已經沒有任何地方會指向它了,tail的previous與header的next都已經變掉了,因此這塊Entry會被當作"垃圾"對待。之因此還要將previous、element、next設置爲null,我認爲多是爲了兼容另一種垃圾回收檢測算法----引用計數法,這種垃圾回收檢測算法,只要對象之間存在相互引用,那麼這塊內存就不會被看成"垃圾"對待。

插入元素

public void add(int index, E element) {
   addBefore(element, (index==size ? header : entry(index)));
}

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;
}

理解了前面的原理,這個應該看的懂了

 

LinkedList和ArrayList的對比

老生常談的問題了,這裏我嘗試以本身的理解儘可能說清楚這個問題,順便在這裏就把LinkedList的優缺點也給講了。

一、順序插入速度ArrayList會比較快,由於ArrayList是基於數組實現的,數組是事先new好的,只要往指定位置塞一個數據就行了;LinkedList則不一樣,每次順序插入的時候LinkedList將new一個對象出來,若是對象比較大,那麼new的時間勢必會長一點,再加上一些引用賦值的操做,因此順序插入LinkedList必然慢於ArrayList

二、基於上一點,由於LinkedList裏面不只維護了待插入的元素,還維護了Entry的前置Entry和後繼Entry,若是一個LinkedList中的Entry很是多,那麼LinkedList將比ArrayList更耗費一些內存

三、數據遍歷的速度,看最後一部分,這裏就不細講了,結論是:使用各自遍歷效率最高的方式,ArrayList的遍歷效率會比LinkedList的遍歷效率高一些

四、有些說法認爲LinkedList作插入和刪除更快,這種說法實際上是不許確的:

(1)LinkedList作插入、刪除的時候,慢在尋址,快在只須要改變先後Entry的引用地址

(2)ArrayList作插入、刪除的時候,慢在數組元素的批量copy,快在尋址

因此,若是待插入、刪除的元素是在數據結構的前半段尤爲是很是靠前的位置的時候,LinkedList的效率將大大快過ArrayList,由於ArrayList將批量copy大量的元素;越日後,對於LinkedList來講,由於它是雙向鏈表,因此在第2個元素後面插入一個數據和在倒數第2個元素後面插入一個元素在效率上基本沒有差異,可是ArrayList因爲要批量copy的元素愈來愈少,操做速度必然追上乃至超過LinkedList

從這個分析看出,若是你十分肯定你插入、刪除的元素是在前半段,那麼就使用LinkedList;若是你十分肯定你刪除、刪除的元素在比較靠後的位置,那麼能夠考慮使用ArrayList。若是你不能肯定你要作的插入、刪除是在哪兒呢?那仍是建議你使用LinkedList吧,由於一來LinkedList總體插入、刪除的執行效率比較穩定,沒有ArrayList這種越日後越快的狀況;二來插入元素的時候,弄得很差ArrayList就要進行一次擴容,記住,ArrayList底層數組擴容是一個既消耗時間又消耗空間的操做

 

遍歷

ArrayList使用最普通的for循環遍歷,LinkedList使用foreach循環比較快,主要是由於ArrayList是實現了RandomAccess接口而LinkedList則沒有實現這個接口

相關文章
相關標籤/搜索