如果世界上只有一种数据结构,那么我选择 hash
點擊上方“朱小廝的博客”,選擇“設為星標”
后臺回復"k8s"領取阿里云《深入淺出k8s.pdf》
來源:rrd.me/gEtR3
最近對hash有了更多深入的理解。這里也寫篇文章專門來聊聊hash。
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進行分組。
關于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操作的一個表示。
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中有現成的實現。
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來記錄訪問頻率。
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);5.hash聚集。
少數情況下,希望通過hash能讓相似的內容在hash過后仍然相似,而不是一點改動便面目全非。
simhash。simhash是一種局部敏感hash,對于google百度這樣的大搜索公司,用空間向量去計算文檔相似度顯得既慢又笨重,simhash用一種相似則海明距離近的方式巧妙而快速的解決了文檔相似的比較。這對hash提出了另一種不同的要求,以往hash函數的目的是為了足夠分散,而這里卻希望hash后呈現一定的規律,實際上個人覺得這更像是一種編碼,根據這種編碼規則,相似的文檔在hash值上的海明距離更近。
6.其他特殊hash。
一致性hash。
一致性hash主要是為了解決傳統的取模為主的hash將數據分配到n臺服務器之后,服務器再擴容或縮容所帶來的所有數據需要重新計算hash的問題。這種情況對于線上某些重要的服務往往是不可接受的。于是一致性hash出現了,它通過將hash值空間預先分配到一個超級大的虛擬節點上,再通過實體節點就近接管虛擬節點來解決映射問題。如圖,這個超級大的虛擬節點即是2^32個,真正的的實體節點只有4個,由于順時針就近映射,每個實體節點都將接管落入前面一個實體節點以后的所有虛擬節點的值,這樣每次擴容時只會影響最多一個節點。一致性hash基本人盡皆知,這里就不列舉資料了。
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。
想知道更多?掃描下面的二維碼關注我
后臺回復”加群“獲取公眾號專屬群聊入口
當當優惠碼福利來一波!當當全場自營圖書5折,用優惠碼:TASEMU(長按復制),滿200(原價400)再減30,相當于170=400,四折多一點。使用渠道:當當小程序或當當APP。使用時間:4/10-4/23。目前優惠碼只有少量了,且不會再增加。
【原創系列 | 精彩推薦】
Paxos、Raft不是一致性算法嘛?
越說越迷糊的CAP
面試官居然問我Raft為什么會叫做Raft!
面試官給我挖坑:URI中的//有什么用
網關Zuul科普
網關Spring Cloud?Gateway科普
分布式事務科普——初識篇
分布式事務科普——終結篇
面試官給我挖坑:a[i][j]和a[j][i]有什么區別?
Nginx架構原理科普
朕已閱?
總結
以上是生活随笔為你收集整理的如果世界上只有一种数据结构,那么我选择 hash的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开放下载!阿里云《深入浅出Kuberne
- 下一篇: 面试官给我挖坑:单台服务器并发TCP连接