比特宇宙編程語言聯合委員會準備舉辦一次大會,主題爲哈希表,給各大編程語言帝國都發去了邀請函。程序員
很快就到了大會這一天編程
聯合委員會祕書長開場發言:「諸位,爲促進技術交流與發展,加強各帝國友誼,聯合委員會特設此盛會,感謝諸位的捧場」數組
會場傳來一陣鼓掌聲······編程語言
祕書長繼續發言:「本次大會的主題是哈希表,人類程序員使用最多的數據容器之一,各大編程語言帝國相信都有實現。今天的大會就圍繞哈希表分爲幾個議題討論,首先是第一個議題:存儲結構與衝突解決」源碼分析
來自GoLang帝國的map
率先發言:「哈希表,哈希表,首先得是個表嘛,因此最基本的要用一個數組來存儲,數組中的每個元素叫作bucket
。至於hash衝突嘛,就用鏈表來解決嘛」測試
GoLang帝國的map說完,有人站了起來:「英雄所見略同!在下C++帝國的unordered_map
,咱們基本上也是選擇的這種方法」線程
此時,Python帝國的表明提出了質疑:「鏈表確實能夠解決衝突,不過嘛,這要是衝突太多,鏈表太長,搜尋起來豈不費時?」code
GoLang帝國的map和C++帝國的unordered_map面面相覷,不知如何應對。對象
「鏈表太長的話,那就轉成樹結構!」,就在這時,又有人站了起來。blog
見有人起身,Python帝國表明轉身問道:「在下乃Python帝國的字典dict{}
,敢問閣下怎麼稱呼」
「我是Java帝國的HashMap
,和前面兩位兄臺的策略大致相同,只是在衝突過多,具體來講鏈表長度超過8的時候就轉換成紅黑樹的結構,以此加快查找」
說完,map、unordered_map鬆了一口氣,和HashMap一塊兒坐下了。
dict{}繼續發問:「在座的都是這個思路,用鏈表解決衝突?」
說完,另一位表明站了起來,「等等,咱們C#帝國的HashTable
就沒用鏈表!」
dict{}露出了滿意的表情,「那大家是怎麼解決衝突的呢?」
「咱HashTable內部使用的是雙重散列法,咱內部不止一種哈希計算方式,一次Hash衝突,咱就換一個再算,直到找到有空位的地方存儲」,HashTable回答到。
dict{}看起來有些失望,估計這也不是他所用的方式。
「你問了半天,還沒說大家Python是怎麼處理衝突的呢?」,Java帝國的HashMap開口了。
「是啊,是啊」,其餘表明也跟着起鬨。
見衆人起鬨,dict{}只好應答:「鏈表法當然不錯,不過須要在插入數據過程當中動態分配內存構建鏈表節點,開銷不小,咱們沒有采用。」
「那到底用了啥,你卻是說啊,快急死我了」,C++的unordered_map有些急了。
「咱們用的是一種叫開放尋址法的策略,若是發現了衝突,就按照制定的策略從這個位置日後找,直到找到有空的位置存儲」,dict{}繼續說到。
「哪有那麼簡單的事,你把別人的位置佔了,那對應那個位置的數據來了怎麼辦?還有查找怎麼找?刪除怎麼處理?這不全亂套了嗎」,unordered_map追問不捨。
「是這樣的,按照咱們既定的規則,在查找的時候就須要額外作一些工做,另外刪除的時候也不能直接刪除,不然會破壞規則鏈條·····」,接下來一段時間,dict{}給你們仔細介紹了他們的處理思路。
「你這個也太麻煩了,不如咱們鏈表法來的清晰明瞭」
「這怎麼就麻煩了?這好處不顯而易見嘛?」,dict{}也不甘示弱。
這時,祕書長打斷了你們的爭辯:「諸位,諸位,靜一靜,靜一靜,我們這個議題到此爲止,進入下一個議題:哈希到位置映射」
急性子的C++帝國表明unordered_map
第一個說話:「這有什麼好討論的,不就是用hash值對哈希表數組長度進行一個求模運算嗎?」
「就是,這有什麼好討論的」,C#帝國的HashTable
也附和到。
「哎,此言差矣,我就沒用取模運算」,衆人望去,這Python帝國的dict{}又要鬧什麼新鮮玩意。
GoLang帝國的map
問道:「老哥用的什麼辦法,別賣關子了,快說來聽聽」
dict{}掃了衆人一眼說到,「個人辦法就是:」
這是怎麼個映射法?衆表明皆摸不着頭腦,議論紛紛,惟有Java帝國的HashMap
聽聞微微一笑。
dict{}見狀問道:「HashMap兄臺,莫非知曉其中玄機?」
只見HashMap不緊不慢的站了起來講到:「哈希表長度是2的冪次,減1以後的二進制均變成了1,好比長度16,減1變成15,也就是二進制1111。再進行與運算,至關於取了哈希值的低位,直接映射到對應的數組位置,與運算比取模運算要快很多。不瞞諸位,我HashMap中也是使用的這種方式,此乃雕蟲小技,不值得炫耀」
衆表明聽完紛紛點頭稱讚,dict{}不知什麼時候卻已坐下。
C#的HashTable
問道:「這樣直接取低幾位,會不會形成Hash值到數組到映射不均勻,拿你舉的例子來講,18的二進制是0001 0010,34的二進制是0010 0010,他們的低4位都同樣,和1111與上之後都是0010,也就是都該存到數組的2號位,這豈不是必定程度上的增長了衝突的機率嗎?」
突如其來的質疑並無讓HashMap慌亂,反而是從容不迫的解釋到:「C#表明的這個問題提的很是好,不知dict{}兄臺是如何處理的。咱們的方案是在進行與運算映射以前,對hash值進行一個處理,具體來講就是將其高16位與低16位進行一個異或運算,如此一來,最終參與與運算的部分就融合了原始hash的所有信息,而不只僅是低位。」
衆表明聽完再次點頭稱讚。
祕書長打破了平靜,「看來你們收穫都頗豐,我們接着下一個話題吧:初始容量與擴容」
衆表明這一次皆不爭先,互相觀望。
祕書長見狀說到:「沒人主動,那我可就要點名了······」
「那就我先吧」,Java帝國的HashMap
站了起來,「個人默認初始容量是16,有一個叫負載因子的參數,默認是0.75。個人策略是,若是內部數組的空間使用了超過75%,那就要準備擴容了,不然後續Hash衝突的機率就會很大。哦對了,擴容時容量得是2的指數次方,緣由前面已經交代了」
dict{}
第二個起身:「嗯,差很少,個人默認初始容量是8,擴容的時候也是要求是2的指數次方,另外個人負載因子是2/3,擴容時機比這位HashMap老哥更早一些」
C#帝國表明HashTable
聽聞也起身發言:「個人初始容量是3,至於負載因子嘛,我通過大量實驗測試,得出的數據在兩位之間,是0.72。容量大小方面我就沒有2的指數次方的要求了,而是要求一個素數。之因此要求素數的緣由,是由於我使用的求模運算進行的映射,使用素數的話,衝突會少一些。」
這時,C++帝國表明unordered_map
也說話了,「巧了!我也是素數哎,你看,我提早把容量都算好存起來了,到時候擴容就挨個取就好了。」
時間過的很快,在你們熱情的討論中,一上午時間很快就結束了。
大會臨近尾聲,祕書長致辭宣佈:「感謝各位表明積極探討,大會取得圓滿成功,本次大會到此結束,我們下次再會!」
會場再次傳來一陣熱烈的鼓掌聲······
然而就在此時,會場外忽然傳來一個聲音:「舉辦如此盛會,怎能少了我」
衆人望去,皆嘆:「他果真仍是來了」
會後,C#帝國表明拉住了C++帝國表明
「兄弟,八卦一下,你這取的是個啥名,你看我和Java帝國的表明都叫Hashxxx,你咋不也叫hash_map或者hash_table之類的名字呢?叫什麼unordered_map」
「哎,兄臺你有所不知,其實我也不想叫這名字,只是,,,這話說來話長了······」,unordered_map嘆了口氣。