数据结构与算法之美-哈希算法
哈希算法的定義和原理
將任意長度的二進制串映射為固定長度的二進制串。
這個映射的規則就是哈希算法,而通過原始數據映射之后得到的二進制串就是哈希值。
設計一個優秀的哈希算法需要滿足:
- 從哈希值不能反向推導出原始數據(所以哈希算法也叫單向哈希算法);
- 對輸入數據非常敏感,哪怕原始數據只修改了一個 Bit,最后得到的哈希值也大不相同;
- 散列沖突的概率要很小,對于不同的原始數據,哈希值相同的概率非常小;
- 哈希算法的執行效率要盡量高效,針對較長的文本,也能快速地計算出哈希值。
?
MD5哈希算法
MD5的哈希值是128位的bit長度,為了方便轉換成了16進制編碼。
可以看出無論哈希值文本有多長多短,通過MD5哈希之后,得到的哈希值的長度都是一樣的,
而且得到的哈希值看起來像一堆隨機數完全沒有規律。
MD5(" 今天我來講哈希算法 ") = bb4767201ad42c74e650c1b6c03d78fa MD5("jiajia") = cd611a31ea969b908932d44d126d195b?
哈希算法的應用
安全加密
加密算法
最常用于加密的哈希算法是MD5(MD5?Message-Digest Algorithm,MD5 消息摘要算法)和SHA(Secure Hash?Algorithm,安全散列算法)。
對用于加密的哈希算法來說,有兩點格外重要:
- 很難根據哈希值反向推導出原始數據,
- 散列沖突的概率要很小。
實際上,不管是什么哈希算法,我們只能盡量減
少碰撞沖突的概率,理論上是沒辦法做到完全不沖突的。
鴿巢理論
這里就基于組合數學中一個非常基礎的理論,鴿巢原理(也叫抽屜原理)。
這個原理本身很簡單,它是說如果有 10 個鴿巢,有 11 只鴿子那肯定有 1 個鴿巢中的鴿子數量多于 1 個。
換句話說就是,肯定有 2 只鴿子在 1 個鴿巢內。
我們知道,哈希算法產生的哈希值的長度是固定且有限的。
哈希值是固定的 128 位二進制串,能表示的數據是有限的最多能表示 2^128 個數據。
基于鴿巢原理,如果我們對 2^128+1 個數據求哈希值,就必然會存在哈希值相同的情況。
?
唯一標識
在圖庫中搜索圖片
如果要在海量的圖庫中,搜索一張圖是否存在,我們不能單純地用圖片的元信息(比如圖片名稱)來比對。
因為有可能存在名稱相同但圖片內容不同,或者名稱不同圖片內容相同的情況。
我們可以給每一個圖片取一個唯一標識,或者說信息摘要。
比如,我們可以從圖片的二進制碼串開頭取 100 個字節,從中間取 100 個字節,從最后再取 100 個字節。
然后將這 300 個字節放到一塊,通過哈希算法(比如 MD5)得到一個哈希字符串,用它作為圖片的唯一標識。
通過這個唯一標識來判定圖片是否在圖庫中,這樣就可以減少很多工作量
性能提升
如果還想繼續提高效率,我們可以把每個圖片的唯一標識和相應的圖片文件在圖庫中的路徑信息,都存儲在散列表中。
當要查看某個圖片是不是在圖庫中的時候,我們先通過哈希算法對這個圖片取唯一標識,然后在散列表中查找是否存在這個唯一標識。
?
數據校驗
P2P文件快校驗
BT 下載的原理是基于 P2P 協議的。
我們從多個機器上并行下載一個 2GB 的電影,這個電影文件可能會被分割成很多文件塊(比如可以分成100 塊,每塊大約 20MB)。
等所有的文件塊都下載完成之后,再組裝成一個完整的電影文件就行了。
網絡傳輸是不安全的,下載的文件塊有可能是被宿主機器惡意修改過的,又或者下載過程中出現了錯誤,所以下載的文件塊可能不是完整的。
解決方法
我們通過哈希算法,對 100 個文件塊分別取哈希值并且保存在種子文件中。
當文件塊下載完成之后,我們可以通過相同的哈希算法,對下載好的文件塊逐一求哈希值,然后跟種子文件中保存的哈希值比對。
如果不同,說明這個文件塊不完整或者被篡改了,需要再重新從其他宿主機器上下載這個文件塊。
?
散列函數
散列函數是一種哈希算法
實際上,散列函數也是哈希算法的一種應用。
散列函數是設計一個散列表的關鍵。
它直接決定了散列沖突的概率和散列表的性能。
不過,相對哈希算法的其他應用,散列函數對于散列算法沖突的要求要低很多。
即便出現個別散列沖突,只要不是過于嚴重,我們都可以通過開放尋址法或者鏈表法解決。
散列函數追求平均分布
不僅如此,散列函數對于散列算法計算得到的值,是否能反向解密也并不關心。
散列函數中用到的散列算法,更加關注散列后的值是否能平均分布。
也就是一組數據是否能均勻地散列在各個槽中。
除此之外,散列函數執行的快慢,也會影響散列表的性能,
所以,散列函數用的散列算法一般都比較簡單,比較追求效率。
?
負載均衡
我們需要在同一個客戶端上,在一次會話中的所有請求都路由到同一個服務器上。
最直接的方法就是,維護一張映射關系表,這張表的內容是客戶端 IP 地址或者會話 ID 與服務器編號的映射關系。
客戶端發出的每次請求,都要先在映射表中查找應該路由到的服務器編號,然后再請求編號對應的服務器。
這種方法簡單直觀,但也有幾個弊端:
- 如果客戶端很多,映射表可能會很大,比較浪費內存空間;
- 客戶端下線、上線,服務器擴容、縮容都會導致映射失效,這樣維護映射表的成本就會很大;
如果借助哈希算法,這些問題都可以非常完美地解決。
我們可以通過哈希算法,對客戶端 IP 地址或者會話 ID 計算哈希值,將取得的哈希值與服務器列表的大小進行取模運算,最終得到的值就是應該被路由到的服務器編號。
這樣,我們就可以把同一個 IP 過來的所有請求,都路由到同一個后端服務器上。
?
數據分片
統計“搜索關鍵詞”出現的次數
假如我們有 1T 的日志文件,這里面記錄了用戶的搜索關鍵詞,
我們想要快速統計出每個關鍵詞被搜索的次數。
我們可以先對數據進行分片,然后采用多臺機器處理的方法,來提高處理速度。
為了提高處理的速度,我們用 n 臺機器并行處理。
我們從搜索記錄的日志文件中,依次讀出每個搜索關鍵詞,
并且通過哈希函數計算哈希值,然后再跟 n 取模,最終得到的值,就是應該被分配到的機器編號。
這樣,哈希值相同的搜索關鍵詞就被分配到了同一個機器上。
也就是說,同一個搜索關鍵詞會被分配到同一個機器上。
每個機器會分別計算關鍵詞出現的次數,最后合并起來就是最終的結果。
快速判斷圖片是否在圖庫中
假設現在我們的圖庫中有 1 億張圖片,在單臺機器上構建散列表是行不通的。
我們同樣可以對數據進行分片,然后采用多機處理。
我們準備 n 臺機器,讓每臺機器只維護某一部分圖片對應的散列表。
我們每次從圖庫中讀取一個圖片,計算唯一標識,
然后與機器個數 n 求余取模,得到的值就對應要分配的機器編號,
然后將這個圖片的唯一標識和圖片路徑發往對應的機器構建散列表。
當我們要判斷一個圖片是否在圖庫中的時候,我們通過同樣的哈希算法,
計算這個圖片的唯一標識,然后與機器個數 n 求余取模。
假設得到的值是 k,那就去編號 k 的機器構建的散列表中查找。
?
分布式存儲
我們有海量的數據需要緩存,所以一個緩存機器肯定是不夠的。
我們需要將數據分布在多臺機器上通過哈希算法對數據取哈希值,然后對機器個數取模,這個最終值就是應該存儲的緩存機器編號
如果數據增多,原來的 10 個機器已經無法承受,我們需要擴容,假如擴到 11 個機器。
原來的數據是通過與 10 來取模的,比如 13 這個數據,存儲在編號為 3 這臺機器上。
新加了一臺機器后,我們對數據按照 11 取模,原來 13 這個數據被分配到了 2 號這臺機器上。
因此,所有的數據都要重新計算哈希值,然后重新搬移到正確的機器上。
所有的數據請求都會穿透緩存,直接去請求數據庫,可能會發生雪崩效應,壓垮數據庫。
我們需要一種方法,使得在新加入一個機器后,并不需要做大量的數據搬移。
這時候,一致性哈希算法就要登場了。
一致性哈希算法
假設我們有 k 個機器,數據的哈希值的范圍是 [0, MAX]。
我們將整個范圍劃分成 m 個小區間(m 遠大于 k),每個機器負責 m/k 個小區間。
當有新機器加入的時候,我們就將某幾個小區間的數據,從原來的機器中搬移到新的機器中。
這樣既不用全部重新哈希、搬移數據,也保持了各個機器上數據數量的均衡。
?
轉載于:https://www.cnblogs.com/errornull/p/9952646.html
總結
以上是生活随笔為你收集整理的数据结构与算法之美-哈希算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GBDT分类和回归例子
- 下一篇: JEMTER简单的测试计划