爲何arrayList.removeAll(set)的速度遠高於arrayList.removeAll(list)?

引言

咱們知道,對於集合(Collection)都有一個抽象方法removeAll(Collection<?> c)!php

可是你可知道,在集合數據比較多的狀況下, ArrayList.removeAll(Set)的速度遠遠高於ArrayList.removeAll(List)css

我簡單測試了一下,從1百萬數據中remove掉30萬數據,前者須要0.031秒,後者須要1267秒!vue

這不是危言聳聽,你們感興趣能夠去實測一下。java

探究

類結構分析

先看一下大概的類結構圖: ios

從圖中能夠看到,圖中相關的集合類( HashSetLinkedListArrayList),除了 ArrayList本身實現了 removeAll()方法外,其餘兩個集合都是藉助父類(或超父類)的Iterator迭代器進行刪除。

也許這也是爲什麼ArrayListremoveAll()方法對於不一樣類型的參數,表現出「不同凡響」的緣由吧~!c++

細嚼代碼

咱們再來細看ArrayList類的removeAll()方法的實現。es6

爲節省各位看官的時間,具體代碼我就不貼出來,貼一個僞代碼吧,更容易閱讀:web

如:list.removeAll(subList);

//1.將list中不刪除的元素移到數組前面(咱們知道ArrayList的底層是數組實現)
int w=0; //w爲不刪除和要刪除的分界線
for(var value in 該list的底層數組){
    if(!subList.contain(value)){
        該list的底層數組[w]=value;
        w++;
    }
}

//2.將w後面的元素所有置爲null
xxx
複製代碼

其中,咱們能夠看到影響速率關鍵的一步:subList.contain(value)npm

因此速率的差別,其實也就在於參數集合.contain()方法的差別~vim

HashSet.contains() vs ArrayList.contains()

  1. ArrayList.contains()

實現很簡單,即調用indexOf(),一個一個地遍歷查找。最壞時間複雜度爲O(總數據量)

  1. HashSet.contains()

咱們知道,HashSet的底層是HashMap,所以,實際也就是調用map.containKey()方法。

你們都知道,HashMap的查找速度很是快!

所以,到這裏,咱們也就解釋題目的問題。同時也知道了,在數據量比較大的的狀況下,使用arrayList.removeAll(subList)時,能夠更改成:

  • subList封裝爲HashSetarrayList.removeAll(new HashSet(subList))
  • arrayList改成LinkedListnew LinkedList(arrayList).removeAll(subList)

再聊HashMap.containKey()

都說到這兒了,不聊聊map的一點東西,也說不過去了。

先上圖:

咱們知道,HashMap的底層是數組+鏈表。而containsKey()底層其實也就是getEntry(key),而後判斷該Entry是否爲null,來達到目的!

在JDK1.8中,getEntry()getNode()。另外,get(key)方法的底層一樣也是(e=getEntry(key))!=null?e.value:null

說多了,咱們迴歸正題。

圖上,最頂行爲一個數組,而每列是一個個鏈表。

每一個元素put進來須要放在哪兒,大概須要這些步驟:

  1. 肯定該key放在數組的哪個索引下:索引位置 = (數組.size-1) & hash(key.hashcode())
  • 以前版本是將上面的位運算&換成了取餘%,效果都同樣,都是爲了防止hashcode值超出數組的長度。不過位運算效率確定是大於取餘的。
  • 科普:a & b = c,那麼c<=min(a,b),所以獲得的索引始終小於數組.size-1,至於爲什麼會小於等於c<=min(a,b)
  • 如:4 & 8 = 00000100 & 00001000,相同位置進行與運算與運算是二者均真才爲真!所以咱們看最小的那個數(00000100),任何數與它進行與運算,前面5位都不可能爲1,那麼結果只能小於等於4~
  • 另外注意,上面用了一個hash()方法,是爲了讓全部key的hash保持均勻,爲何要這樣作呢?
  • 舉個例子,你重寫了hashcode方法,返回都是1。最後hashmap在存儲這類對象時,全都放到同一個索引位置去了!
  1. 給Entry.next=null的Entry,變爲Entry.next=new Node()
  • 注意:若是數據過大,JDK1.8會自動切換鏈表爲紅黑樹實現

所以,就containsKey()而言,最壞的時間複雜度爲:O((總數據量/數組長度)*最長鏈表長度)

而這個數組長度到底有多長?鏈表有多長?它們和數據量成一個什麼關係呢?

咱們須要簡單探究一下HashMap的實現:

由圖可知,數組長度通常都是大於總數據量(負載因子<=1時)。所以最壞時間複雜度≈O(最長鏈表長度)。

那麼鏈表長度有多長?

設想一下,數組長度>=總數據量,那麼最好狀況下(各數據的hash均勻分佈),可能一個鏈表就一個元素,即時間複雜度可能爲O(1)!

至少大多狀況下,鏈表長度都不會太長,除非你就是那個重寫hashcode,始終返回1的人!

相關文章
相關標籤/搜索