數據結構——哈希算法

最近開始學習王爭老師的《數據結構與算法之美》,通過總結再加上自己的思考的形式記錄這門課程,文章主要作爲學習歷程的記錄。

哈希算法的定義是將任意長度的二進制值串映射爲固定長度的二進制值串。這個映射規則就是哈希算法。通過原始數據映射後得到的二進制值串就是哈希值。設計一個優秀的哈希算法應滿足幾點要求:

1.從哈希值不能反向推導出原始數據(因此哈希算法也叫單向哈希算法)。

2.對輸入數據非常敏感,哪怕原始數據只修改了一個Bit,最後得到的哈希值也大不相同。

3.散列衝突的概率要很小,對於不同的原始數據,哈希值相同的概率非常小。

4.哈希算法的執行效率要儘量高效,針對較長的文本,也能快速計算出哈希值。

舉個例子,

MD5(“今天我來講哈希算法”) = bb4767201ad42c74e650c1b6c03d78fa

MD5(“jiajia”) = cd611a31ea969b908932d44d126d195b

可以看出,無論哈希的文本有多長,多短,通過MD5哈希之後,得到的哈希值長度是相同的,而且得到的哈希值看起來像是一堆隨機數,毫無規律,也很難根據哈希值反推回對應的文本。

哈希算法的應用非常非常多,作者選了最常見的7個,分別是安全加密、唯一標識、數據校驗、散列函數、負載均衡、數據分片和分佈式存儲。

應用一:安全加密

最常用於加密的哈希算法是MD5(MD5消息摘要算法)和SHA(安全散列算法)。除此之外,還有其他加密算法,比如DES(數據加密算法)、AES(高級加密算法)。

對於加密的哈希算法,有兩點格外重要。第一點是很難根據哈希值反向推導出原始數據,第二點是散列衝突的概率要很小。第一點容易理解,爲了防止原始數據泄露。第二點需要對爲什麼哈希算法無法做到零衝突進行解釋一下:哈希算法產生的哈希值的長度是固定且有限的。以MD5爲例,哈希值是固定的128位二進制串,能表示的數據是有限的,最多表示2128個數據,而我們要哈希的數據是無窮的。如果對2128+1個數據求哈希值,就必然存在哈希值相同的情況。一般情況下,哈希值越長的哈希算法,散列衝突的概率越低。

沒有絕對安全的加密。越複雜、越難破解的加密算法,需要的計算時間也越長。在實際開發過程中,也需權衡破解難度和計算時間,來決定究竟使用哪種加密算法。

應用二:唯一標識

以圖片爲例,如果要在圖庫中搜索一張圖是否存在,不能單純地用圖片的元信息來比對。比較笨的辦法就是拿要查找的圖片的二進制碼串與圖庫中所有圖片的二進制碼串一一比對,但這種比對非常耗時。我們可以給每一個圖片取一個唯一標識,或者說信息摘要。比如,我們可以從圖片的二進制碼串開頭取100個字節,從中間取100個字節,從最後再取100個字節。然後將這300個字節放在一起,通過哈希算法(如MD5),得到一個哈希字符串,用它作爲圖片的唯一標識,這樣可以減少很多工作量。

應用三:數據校驗

以電驢BT下載軟件爲例,其下載原理是基於P2P協議。從很多機器並行下載一個2GB的電影,這個電影文件可能會被分割成很多文件塊。等所有的文件塊都下載完成後,再組裝成一個完整的電影文件就行了。

但是網絡傳輸是不安全的,下載過程中可能出現錯誤,因此需要校驗文件塊的安全,正確以及完整。具體的BT協議很複雜,校驗方法也很多。就說一下其中的一種思路:通過哈希算法,對100個文件塊分別取哈希值,並且保存在種子文件中。哈希算法對數據很敏感,只要文件塊內容有一些變化,最後計算出的哈希值完全不同。所以,當文件塊下載完成後,可以通過相同的哈希算法,對下載好的文件塊逐一求哈希值。然後再跟種子文件中保存的哈希值比對。如果不同,則說明文件塊在下載過程中出現錯誤。

應用四:散列函數

散列函數也是哈希算法的一種應用。散列函數是設計一個散列表的關鍵,直接決定了散列衝突的概率和散列表的性能。但相比於哈希表其他應用,散列函數對於散列算法衝突的要求要低很多。即使出現了個別散列衝突,只要不是過於嚴重,都可以通過開放尋址法或鏈表法。

散列函數對於能否反向解密並不關心。它更關注散列後的值能否平均分佈。此外,散列函數執行的快慢,也會影響散列表的性能。故散列函數用的散列算法一般都比較簡單,比較追求效率。

應用五:負載均衡

如何實現一個會話沾滯的負載均衡算法?也就是說,我們需要在同一個客戶端上,在一次會話中所有請求都路由到同一個服務器上。

最直接的方法就是維護一張映射關係表,內容是客戶端IP地址或會話ID與服務器編號的映射關係。客戶端發出的每次請求,都要先在映射表中查找應該路由到服務器的編號,然後再請求編號對應的服務器,但有幾個弊端:

1、如果客戶端很多,映射表可能會很大,比較浪費內存空間。

2、客戶端下線,上線,服務器擴容、縮容都會導致映射失效,這樣維護映射表的成本就會很大。

但是如果藉助哈希算法,對客戶端IP地址或會話ID計算哈希值,將取得的哈希值與服務器列表的大小進行取模運算,最終得到的值就應該被路由到服務器編號。

應用六:數據分片

哈希算法可以用於數據的分片,舉兩個例子:

1、如何統計“搜索關鍵詞”出現的次數?

假如有1T的日誌文件,記錄了用戶的搜索關鍵詞,我們想要快速統計出每個關鍵詞被搜索的次數。這個問題有兩個難點:第一個是搜索日誌過大,沒辦法放到一臺機器的內存中。第二個是如果只用一臺機器來處理這麼巨大的數據,處理時間會很長。

針對這兩個難點,我們可以先對數據進行分片,然後採用多臺機器處理的方法,來提高處理速度。具體的思路:爲了提高處理的速度,我們用n臺機器並行處理。我們從搜索記錄的日誌文件中,依次讀出每個搜索關鍵詞,並且通過哈希函數計算哈希值,然後再跟n取模,最終得到的值,就是應該被分配的機器編號。

這樣,哈希值相同的搜索關鍵詞就分配到同一個機器上,每一個機器分別計算關鍵詞出現的次數,合併起來就是最終結果。

2、如何快速判斷圖片是否在圖庫中?

同樣對數據進行分片,採用多機處理。每次從圖庫中讀取一個圖片,計算唯一標識,然後與機器個數n求餘取模,得到的值就對應要分配的機器編號,然後將這個圖片的唯一標識和圖片路徑發往對應的機器構建散列表。

當判斷一個圖片是否在圖庫中時,同樣通過哈希算法,計算這個圖片的唯一標識,然後與機器個數n求餘取模。假設得到的值是k,那就去編號k的機器構建的散列表中查找。

估算一下,1億個圖片構建散列表大約需要多少臺機器?假設採用MD5來計算哈希值,那長度爲128比特,也就是16字節。文件路徑長度上限是256字節,我們假設平均長度是128字節。如果用鏈表法解決衝突,還需要存儲指針,指針只佔用8個字節。假設一臺機器的內存大小爲2GB,散列表裝載因子爲0.75,故大約1000萬(2GB0.75/1522GB*0.75/152)張圖片構建散列表。

針對這種海量數據的處理問題,都可以採用多機分佈式處理。

應用七:分佈式存儲

爲了提高數據的讀取、寫入能力,一般都採用分佈式的方式來存儲數據,比如分佈式緩存。如果有大量數據需要緩存,需要將數據分佈在多臺機器上。借用數據分片的思想,即通過哈希算法對數據取哈希值,然後對機器個數取模,這個最終值就是應存儲的緩存機器編號。

但如果數據增多,原來的10個機器已經無法承受,就需要擴容了。比如擴容到11個機器,這時候麻煩就來了。所有的數據都需要重新計算哈希值。然後重新搬移到正確的機器上,這就相當於緩存中的數據一下子就都失效了。所有的數據請求都會穿透緩存,直接去請求數據庫,這樣就可能壓垮數據庫。

因此,需要一種方法,使得在新加入一個機器後,並不需要做大量的數據搬移——一致性哈希算法。

假設有k個機器,數據的哈希值的範圍是[0,MAX]。我們將整個範圍劃分成m個小區間(m遠大於k),每個機器負責m/k個小區間。當有新機器加入的時候,就將某幾個小區間數據從原來的機器中搬移到新的機器中,這樣,既不用全部重新哈希,搬移數據,也保持了各機器上數據數量均衡。

參考資料:王爭《數據結構與算法之美》

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