如果世界上只有一種數據結構,我選擇哈希!

Hash是一種常見的數據結構或者說計算方法,以其O(1)的時間算法複雜度聞名於世。曾有人說,如果世界上只有一種數據結構,那麼我選擇hash,足見hash的地位及牛逼之處,而代碼編寫中hash也屢見不鮮,因爲他實在是太常見太好用了。

但是實際使用過程中,基本的hash是遠遠不夠的,按照用途,對hash其實還有如下需求:

關於java中hash的數據結構

1.併發安全

對這個需求,java中有了HashTable,爲了進一步提升性能,於是有了使用分段鎖的ConcurrentHashMap,亦不做贅述。

2.大數據hash

傳統的HashMap中除了key, value外,每個entry還要存16個byte的class header,4byte的hash值,以及8byte的指向下一個元素的指針,這樣的結構在遇到大數據量時就會更加耗內存,更容易導致GC。

由對象頭過大可以看出來,只要能夠有一種結構消滅這個額外的entry對象,則此處將大大減少內存的消耗。

一種可行的方式是:採用二級索引保存的方式,第一級索引由Short2ShortMap保存一個short爲key且short爲value的Map結構,第二級索引則由許多數組構成,這些數組負責將被消滅value這個Object拆解爲基本類型並用多個數組保存,而一級索引的value保存的value正是二級數組的index。通過這種變換,消滅了額外的entry對象從而大幅減少內存。

需要注意的是,這種方式適用於使用了大量HashMap,但是每個Map內數據量較小的情況(受short的限制只有3w多index),如果每個Map內數據量也比較大,可以考慮Int2IntMap,當然,這樣減少內存佔用的效果就不如Short2ShortMap了。

3.其他

ImmutableMap,Guava庫,在初始化完畢後就沒法再put做改變了。

SortedMap,Guava庫,數據會按key做字母化排序。

BiMap,Guava庫,創建完之後可以使用inverse將value和key顛倒過來,前提是保證value也是唯一的。

MultiMap,Guava庫,可以對每個key關聯多個值,並且可以很方便的對list進行分組。

另外,關注公衆號Java技術棧,在後臺回覆:面試,可以獲取我整理的 Java 集合、多線程、JVM 系列面試題和答案,非常齊全。

關於hash的一些解決方案

Hash衝突:

衆所周知,解決hash衝突最好的辦法自然是提升hash table的總數量(即N的大小),如果待存放元素的數量k遠小於N,則hash後有更大概率佔據空槽,而衝突越少則性能越好,本質上,這是一種以空間換時間的方式。然而現實中,存儲空間也很寶貴,任何公司都很難接受讓大量空間浪費。於是,便出現了儘可能增加空間佔用但不過分降低性能的hash。

布穀hash:

布穀hash是一種解決衝突的方法。不同於線性探測,開放定址這樣的常規方法,布穀hash借鑑了布穀鳥占人巢穴生子的寓意。其算法比較簡單,採用兩個(或多個)hash函數F1和F2,put操作時用F1或F2計算hashcode並定位,如果任意位置爲空,則插入;否則擠佔其中一個位置,並將被擠佔的元素拿出並重復該過程;而get操作則讓人比較困惑,到底採用哪個函數來get值呢?實際上布穀hash需要在value中存放key值,這樣對於兩個函數get到的值只要判斷中間key是否正確就可以確認其對應的hash函數。布穀hash在二維時空間利用率較高,約爲80%-90%。下圖是對put操作的一個表示。

參考:https://coolshell.cn/articles/17225.html

bloomfilter:

布隆過濾器是一種佔小空間且效率很高的算法,通常用來解決垃圾郵件識別,緩存擊穿及日活計算等場景。bloomfilter只能判斷一個元素可能在其中或者一個元素一定不在其中。

它的算法也採用多個hash函數,如下例,某數據A經過x函數可以映射到4,9兩個位置,經過z函數可以映射到9,14兩個位置,經過y函數可以映射到14,19兩個位置。於是基本的增加操作便可以將這幾個對應位置的值置爲1;對於基本的查找操作,則對A進行hash後找到其所有對應位置,發現其所有對應位置都是1,則表示A很可能存在,爲什麼不能確定呢,因爲有可能這些位置並不是對A進行hash後對應的位置,有可能是插入了BCDE等數據而這些數據剛好覆蓋了A的所有位置而導致的,所以發現全1僅僅能判斷其可能存在;但是一旦有任意對應位置爲0,則表示A一定不存在。對於基本的刪除和更新操作,布隆過濾器是不支持的,本質原因是位置是多數據共享的,任何對數據的逆向操作都會導致其他數據的不準。布隆過濾器在Guava中有現成的實現。

參考:https://juejin.im/post/5de1e37c5188256e8e43adfc

Count–min sketch:

Count-min sketch旨在解決流式大數據下做計數統計時間空間複雜度過高的問題。設想這樣一個場景,線上數據源源不斷的進來,現在我們需要去統計cache中每個ip請求的大致數量,從而確定哪個ip來的請求是hot的。碰到這個問題,可能本能的會想用HashMap,用ip作爲key,用總count當做value。但實際上當數據量足夠大時,各種噩夢就來了,比如每臺機器內存非常高(對應上面說到的大數據hash帶來的問題),hash衝突也變高,rehash成本也會迅速增加,並且在實時響應的要求下,時間上就很可能無法滿足需求,Count-min sketch算法就是爲此而生的。

count-min sketch算法思想比較簡單,採用n個數組以及n個hash函數,對同一個數據用不同的hash函數做hash,分配到這n個數組不同的位置,存值時這個位置所在的value加1,取值時取這n個位置最小值,則此最小值大致接近實際總count數,且總是大於等於實際的總count數。爲啥要取最小值,並且爲啥結果總是大於等於實際總count數呢,原因其實與bloomfilter比較像,有可能有其他的hash也落到了該位置並加了count。參考下圖。在java中,著名的caffeine緩存框架中的W-TinyLFU就用的Count-min sketch來記錄訪問頻率

參考:https://www.cnblogs.com/liujinhua306/p/9808500.html

4.hash分散。大多數情況下,希望hash之後的結果越分散越無規律越好。

Murmur hash。Murmur哈希是一種比大多數算法更爲分散更無規律的算法。

java中的hash算法稱爲Horner,簡單表示就是

for (int i = 0; i < str.length(); i++) { hash = 31*hash + str.charAt[i]; }

實際計算時經常使用移位操作。

Murmur的意思是multiply and rotate,主要優點是速度快且hash值足夠分散,目前已經在各大框架廣泛使用,比如redis,memcache,cassandra,lucene,如下是其簡單表示。

x *= m; x = rotate_left(x,r);

具體算法可參考:https://zh.wikipedia.org/zh-cn/Murmur%E5%93%88%E5%B8%8C

關於Murmur hash的科普參考這裏:http://calvin1978.blogcn.com/articles/murmur.html

5.hash聚集。少數情況下,希望通過hash能讓相似的內容在hash過後仍然相似,而不是一點改動便面目全非。

simhash:

simhash是一種局部敏感hash,對於google百度這樣的大搜索公司,用空間向量去計算文檔相似度顯得既慢又笨重,simhash用一種相似則海明距離近的方式巧妙而快速的解決了文檔相似的比較。

這對hash提出了另一種不同的要求,以往hash函數的目的是爲了足夠分散,而這裏卻希望hash後呈現一定的規律,實際上個人覺得這更像是一種編碼,根據這種編碼規則,相似的文檔在hash值上的海明距離更近。

算法這裏不再贅述,可參考:https://wizardforcel.gitbooks.io/the-art-of-programming-by-july/content/06.03.html

6.其他特殊hash

一致性hash:

一致性hash主要是爲了解決傳統的取模爲主的hash將數據分配到n臺服務器之後,服務器再擴容或縮容所帶來的所有數據需要重新計算hash的問題。這種情況對於線上某些重要的服務往往是不可接受的。於是一致性hash出現了,它通過將hash值空間預先分配到一個超級大的虛擬節點上,再通過實體節點就近接管虛擬節點來解決映射問題。

如圖,這個超級大的虛擬節點即是2^32個,真正的的實體節點只有4個,由於順時針就近映射,每個實體節點都將接管落入前面一個實體節點以後的所有虛擬節點的值,這樣每次擴容時只會影響最多一個節點。一致性hash基本人盡皆知,這裏就不列舉資料了。

另外,關注公衆號Java技術棧,在後臺回覆:面試,可以獲取我整理的 Java 集合、多線程、JVM 系列面試題和答案,非常齊全。

Perfect hash:

perfect hash目的是爲了實現完全無衝突的hash。perfect hash分爲兩種,一種是靜態hash,一種是動態hash;對於靜態hash而言,一個最好的例子就是數組,比如總的值有10個,取hash值後分別映射到3,8,13,18,22,44,53,63,78,92這10個位置,則我們用一個長度爲100的數組可以實現該值域的靜態perfect hash。但是你可能會發現有多餘的位置並沒有被用上,如果能實現長度10的數組完美映射這10個數字,則稱之爲最小完美hash。動態perfect hash一般比較麻煩,需要做二次hash映射並要第二次映射不會衝突,有興趣可以查閱相關資料。

GeoHash:

GeoHash是比較特殊的hash應用,主要是用來快速定位。其原理相對簡單(實現起來有不少細節)。主要就是將每一級的地圖劃分爲32塊,即每一級用5bit來標識(爲啥是5bit,因爲最後用base32的編碼方式,每個字母或數字5bit),每次縮放一級則用另一個字母或數字標識,最終能得到一串字符串wx4gjk32kfrx,從而一級一級定位直到最小那一級。如劃分10級,則最後字符串長度爲4,範圍到20km,如劃分20級,則最後字符串長度爲8,範圍可以精確到19m。

參考:https://zhuanlan.zhihu.com/p/35940647

作者:liweisnake
本文鏈接:https://blog.csdn.net/liweisnake/article/details/104779497
版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。



請添加編  回覆關鍵詞:數據可視化

進羣一起學習交流吧


-今日互動-


你get到了嗎?歡迎文章下方留言互動




如果感覺對你有幫助的話


            
            
            
來個「 轉發朋友圈 」和「 在看 」,一起見證你的努力和成長,是對我們最大的支持!


本文分享自微信公衆號 - DataScience(DataScienceTeam)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章