1. 程式人生 > >雜湊表哪家強?幾大程式語言吵起來了!

雜湊表哪家強?幾大程式語言吵起來了!

## 雜湊表華山論劍 位元宇宙程式語言聯合委員會準備舉辦一次大會,主題為**雜湊表**,給各大程式語言帝國都發去了邀請函。 ![](https://imgkr.cn-bj.ufileos.com/6c7363f0-66c9-4447-bbd5-f694ca3d821e.jpeg) 很快就到了大會這一天 聯合委員會祕書長開場發言:“諸位,為促進技術交流與發展,增強各帝國友誼,聯合委員會特設此盛會,感謝諸位的捧場” 會場傳來一陣鼓掌聲······ ![](https://imgkr.cn-bj.ufileos.com/58c5d6a0-863d-4377-b70b-c89f01798d2c.png) 祕書長繼續發言:“本次大會的主題是**雜湊表**,人類程式設計師使用最多的資料容器之一,各大程式語言帝國相信都有實現。今天的大會就圍繞雜湊表分為幾個議題討論,首先是第一個議題:**儲存結構與衝突解決**” ## 儲存結構與衝突解決 來自GoLang帝國的`map`率先發言:“雜湊表,雜湊表,首先得是個表嘛,所以最基本的要用一個數組來儲存,陣列中的每一個元素叫做`bucket`。至於hash衝突嘛,就用連結串列來解決嘛” ![](https://imgkr.cn-bj.ufileos.com/b0c1ca64-ff9f-4a9c-8dea-be5229d7ba26.png) GoLang帝國的map說完,有人站了起來:“英雄所見略同!在下C++帝國的`unordered_map`,我們基本上也是選擇的這種方法” 此時,Python帝國的代表提出了質疑:“連結串列確實可以解決衝突,不過嘛,這要是衝突太多,連結串列太長,搜尋起來豈不費時?” GoLang帝國的map和C++帝國的unordered_map面面相覷,不知如何應對。 “連結串列太長的話,那就轉成樹結構!”,就在這時,又有人站了起來。 見有人起身,Python帝國代表轉身問道:“在下乃Python帝國的字典`dict{}`,敢問閣下怎麼稱呼” “我是Java帝國的`HashMap`,和前面兩位兄臺的策略大體相同,只是在衝突過多,具體來說連結串列長度超過8的時候就轉換成紅黑樹的結構,以此加快查詢” ![](https://imgkr.cn-bj.ufileos.com/6eff814d-04dc-4962-9d9b-9b00b13af6a2.png) 說完,map、unordered_map鬆了一口氣,和HashMap一起坐下了。 dict{}繼續發問:“在座的都是這個思路,用連結串列解決衝突?” 說完,另外一位代表站了起來,“等等,我們C#帝國的`HashTable`就沒用連結串列!” dict{}露出了滿意的表情,“那你們是怎麼解決衝突的呢?” “咱HashTable內部使用的是雙重雜湊法,咱內部不止一種雜湊計算方式,一次Hash衝突,咱就換一個再算,直到找到有空位的地方儲存”,HashTable回答到。 dict{}看起來有些失望,估計這也不是他所用的方式。 “你問了半天,還沒說你們Python是怎麼處理衝突的呢?”,Java帝國的HashMap開口了。 “是啊,是啊”,其他代表也跟著起鬨。 見眾人起鬨,dict{}只好應答:“連結串列法固然不錯,不過需要在插入資料過程中動態分配記憶體構建連結串列節點,開銷不小,我們沒有采用。” “那到底用了啥,你倒是說啊,快急死我了”,C++的unordered_map有些急了。 “我們用的是一種叫**開放定址法**的策略,如果發現了衝突,就按照制定的策略從這個位置往後找,直到找到有空的位置儲存”,dict{}繼續說到。 ![](https://imgkr.cn-bj.ufileos.com/fbd5949e-997f-49c2-864a-ad010660f78d.png) “哪有那麼簡單的事,你把別人的位置佔了,那對應那個位置的資料來了怎麼辦?還有查詢怎麼找?刪除怎麼處理?這不全亂套了嗎”,unordered_map追問不捨。 “是這樣的,按照我們既定的規則,在查詢的時候就需要額外做一些工作,另外刪除的時候也不能直接刪除,否則會破壞規則鏈條·····”,接下來一段時間,dict{}給大家仔細介紹了他們的處理思路。 “你這個也太麻煩了,不如我們連結串列法來的清晰明瞭” “這怎麼就麻煩了?這好處不顯而易見嘛?”,dict{}也不甘示弱。 ![](https://imgkr.cn-bj.ufileos.com/080d1324-855d-40d8-ab15-ea8257acbedd.png) 這時,祕書長打斷了大家的爭辯:“諸位,諸位,靜一靜,靜一靜,咱們這個議題到此為止,進入下一個議題:**雜湊到位置對映**” ## 雜湊到位置對映 急性子的C++帝國代表`unordered_map`第一個說話:“這有什麼好討論的,不就是用hash值對雜湊表陣列長度進行一個求模運算嗎?” ![](https://imgkr.cn-bj.ufileos.com/f1fc7ac6-8d33-4e39-992c-94a3750551f0.png) “就是,這有什麼好討論的”,C#帝國的`HashTable`也附和到。 “哎,此言差矣,我就沒用取模運算”,眾人望去,這Python帝國的dict{}又要鬧什麼新鮮玩意。 GoLang帝國的`map`問道:“老哥用的什麼辦法,別賣關子了,快說來聽聽” dict{}掃了眾人一眼說到,“我的辦法就是:” ![](https://imgkr.cn-bj.ufileos.com/c25ef07f-7068-4292-a02b-fa4e969170ea.png) 這是怎麼個對映法?眾代表皆摸不著頭腦,議論紛紛,唯有Java帝國的`HashMap`聽聞微微一笑。 dict{}見狀問道:“HashMap兄臺,莫非知曉其中玄機?” 只見HashMap不緊不慢的站了起來說到:“雜湊表長度是2的冪次,減1之後的二進位制均變成了1,比如長度16,減1變成15,也就是二進位制1111。再進行與運算,相當於取了雜湊值的低位,直接對映到對應的陣列位置,與運算比取模運算要快不少。不瞞諸位,我HashMap中也是使用的這種方式,此乃雕蟲小技,不值得炫耀” ![](https://imgkr.cn-bj.ufileos.com/a713f1e8-f7e8-48d8-9c61-d8602c0f97e0.png) 眾代表聽完紛紛點頭稱讚,dict{}不知何時卻已坐下。 C#的`HashTable`問道:“這樣直接取低幾位,會不會造成Hash值到陣列到對映不均勻,拿你舉的例子來說,18的二進位制是0001 0010,34的二進位制是0010 0010,他們的低4位都一樣,和1111與上以後都是0010,也就是都該存到陣列的2號位,這豈不是一定程度上的增加了衝突的概率嗎?” 突如其來的質疑並沒有讓HashMap慌亂,反而是從容不迫的解釋到:“C#代表的這個問題提的非常好,不知dict{}兄臺是如何處理的。我們的方案是在進行與運算對映之前,對hash值進行一個處理,具體來說就是將其高16位與低16位進行一個異或運算,如此一來,最終參與與運算的部分就融合了原始hash的全部資訊,而不僅僅是低位。” ![](https://imgkr.cn-bj.ufileos.com/a626987b-4a4a-4d64-af48-707f230d5132.png) 眾代表聽完再次點頭稱讚。 祕書長打破了平靜,“看來大家收穫都頗豐,咱們接著下一個話題吧:**初始容量與擴容**” ## 初始容量與擴容 眾代表這一次皆不爭先,互相觀望。 祕書長見狀說到:“沒人主動,那我可就要點名了······” “那就我先吧”,Java帝國的`HashMap`站了起來,“我的預設初始容量是16,有一個叫**負載因子**的引數,預設是0.75。我的策略是,如果內部陣列的空間使用了超過75%,那就要準備擴容了,否則後續Hash衝突的概率就會很大。哦對了,擴容時容量得是2的指數次方,原因前面已經交代了” ![](https://imgkr.cn-bj.ufileos.com/4a4dd8b5-ede4-4232-98d6-e0364998c0bb.png) `dict{}`第二個起身:“嗯,差不多,我的預設初始容量是8,擴容的時候也是要求是2的指數次方,另外我的負載因子是2/3,擴容時機比這位HashMap老哥更早一些” C#帝國代表`HashTable`聽聞也起身發言:“我的初始容量是3,至於負載因子嘛,我經過大量實驗測試,得出的資料在兩位之間,是0.72。容量大小方面我就沒有2的指數次方的要求了,而是要求一個**素數**。之所以要求素數的原因,是因為我使用的求模運算進行的對映,使用素數的話,衝突會少一些。” 這時,C++帝國代表`unordered_map`也說話了,“巧了!我也是素數哎,你看,我提前把容量都算好存起來了,到時候擴容就挨個取就行了。” ![](https://imgkr.cn-bj.ufileos.com/7bbc147c-ef14-412f-bd6f-63c36460eb01.png) ## 尾聲 時間過的很快,在大家熱情的討論中,一上午時間很快就結束了。 大會臨近尾聲,祕書長致辭宣佈:“感謝各位代表積極探討,大會取得圓滿成功,本次大會到此結束,咱們下次再會!” 會場再次傳來一陣熱烈的鼓掌聲······ 然而就在此時,會場外突然傳來一個聲音:“舉辦如此盛會,怎能少了我” 眾人望去,皆嘆:“他果然還是來了” ## 彩蛋 > 會後,C#帝國代表拉住了C++帝國代表 > > “兄弟,八卦一下,你這取的是個啥名,你看我和Java帝國的代表都叫**Hashxxx**,你咋不也叫**hash_map**或者**hash_table**之類的名字呢?叫什麼**unordered_map**” > > “哎,兄臺你有所不知,其實我也不想叫這名字,只是,,,這話說來話長了······”,unordered_map嘆了口氣。 ## 往期熱門回顧 [核心地址空間大冒險4:執行緒切換](https://mp.weixin.qq.com/s/v6nc9aIBY_R1S6ToPzj5Qg) [震撼!全網第一張原始碼分析全景圖揭祕Nginx](https://mp.weixin.qq.com/s/XrtH9-Eo7pzJu-Fzt89voQ) [一個整數+1引發的災難](https://mp.weixin.qq.com/s/gZPxqZzY2rnngxvvzexWTw) [一網打盡!每個程式猿都該瞭解的黑客技術大彙總](https://mp.weixin.qq.com/s/V7wBdl-5W4ehTAnACQFjGQ) [看過無數Java GC文章,這5個問題你也未必知道!](https://mp.weixin.qq.com/s/Bb2ugXYPR6r11QaGKbNBSw) [一個Java物件的回憶錄:垃圾回收](https://mp.weixin.qq.com/s/xp2S4_3UQTZ0TOIlVqM8uw) [誰動了你的HTTPS流量?](https://mp.weixin.qq.com/s/lxpHhHVIh6DktoHzrRLaKA) [路由器裡的廣告祕密](https://mp.weixin.qq.com/s/7gM31s4-hTJTprJnxsHgEA) [DDoS攻擊:無限戰爭](https://mp.weixin.qq.com/s/JTr1-5nPtseAYXfvJdamVg) [一條SQL注入引出的驚天大案](https://mp.weixin.qq.com/s/lerhjpAEdp4RiwsmetyqPg) [一個HTTP資料包的奇幻之旅](https://mp.weixin.qq.com/s/suzicCzb2g5b8NN71S5Ngw) [一個DNS資料包的驚險之旅](https://mp.weixin.qq.com/s/_TOFIPGIeMHhVxIVToxmiQ) [我是一個流氓軟體執行緒](https://mp.weixin.qq.com/s/-ggUa3aWkjjHjr9VwQL9TQ) ###
掃碼關注,更多精彩
--- ![](https://imgkr.cn-bj.ufileos.com/5de7751a-9e38-4718-994a-6136f7804ae5.png)