開發面試Hash原理詳解


Hash部分分爲三部分講解,各位遊客可根據分類進行對應博客閱讀:

  1. 開發面試Hash原理詳解
  2. 開發面試Hash常見算法
  3. 開發面試Hash面試考題

博客書寫不易,您的點贊收藏是我前進的動力,千萬別忘記點贊、 收藏 ^ _ ^ !

1. 什麼是Hash

Hash也稱散列、哈希,對應的英文都是Hash,它既是一種重要的存儲方式(散列),也是一種常見的檢索方法。

Hash可以有多重理解和定義

  • 它是基於快速存取的角度設計的,也是一種典型的“空間換時間”的做法。顧名思義,該數據結構可以理解爲一個線性表,但是其中的元素不是緊密排列的,而是可能存在空隙。

Hash方法的主要思想是根據結點的關鍵碼值來確定其存儲地址,它有兩個重要的概念散列表(hash table )散列函數(hash function)

以元素X的key爲自變量,通過一定的函數關係h(X.key)(稱爲散列函數),計算出對應的函數hash值來(hash值就是key的指紋,是一個二進制串),那麼hash值就是在hash table中的存儲地址,改地址名叫散列地址

同樣,在檢索時用同樣的方法計算hash值,然後到相應的槽去取要找的結點。通過散列方法可以對結點進行快速高效檢索。

2. 散列表

數組的特點是:尋址容易,插入和刪除困難;而鏈表的特點是:尋址困難,插入和刪除容易。那麼我們能不能綜合兩者的特性,做出一種尋址容易,插入刪除也容易的數據結構?
答案是肯定的,這就是我們要提起的哈希表,哈希表有多種不同的實現方法。

散列表

又名hash table,是按散列存儲方式構造的一種物理存儲結構,因應用了哈希算法被稱爲哈希表。它是一種線性數據結構,擁有查找速度快的特性。

它的實現邏輯是這樣的:根據設定的哈希函數H(key)和處理衝突方法將一組關鍵字映射到一個有限的地址區間上,並以關鍵字在地址區間中的象作爲記錄在表中的存儲位置

它通過把關鍵碼值映射到表中一個位置即哈希地址,這種通過哈希地址直接進行訪問的數據結構能夠加快數據的查找速度。

補充:
1. 物理存儲結構共4種:順序、鏈式、索引、散列
2. 其中順序和鏈式最常見,這兩種存儲結構的共同特徵是元素之間有着映射關係
3. 而哈希表(散列存儲結構)的元素之間相互獨立。
4. 索引存儲結構類似現實世界中的字典目錄。


哈希表中的一個單元稱作槽(slot),它的對應地址被叫做散列地址/哈希地址。

負載因子
如我們存儲70個元素,但我們可能爲這70個元素申請了100個元素的散列表空間。70/100=0.7,0.7即爲負載因子,其範圍0~1,也叫裝填因子。

一般情況下,散列表的存儲空間是一個一維數組HT[M],散列地址是數組的下標。設計散列方法的目標,就是設計某個散列函數h,0<=h( K ) < M;對於關鍵碼值K,得到HT[i] = K。建立散列表時,若關鍵碼與散列地址是一對一的關係。
例如:給定一個字符串參數 “str”,該鍵對應的元素是"jack",通過hash算法對"str"進行加工生成的一個存儲地址,其對應內容存儲着"jack"

使用hash table 存取元素時不會像傳統的數據結構逐個遍歷、一一對比,而是通過哈希算法直接獲取元素的存儲地址,因此哈希表會比傳統的數據結構更爲高效,這也是使用哈希表的原因。
在一般情況下,散列表的空間必須比結點的集合大,此時雖然浪費了一定的空間,但換取的是檢索效率。

設散列表的空間大小爲M,填入表中的結點數爲N,則稱爲散列表的負載因子(load factor,也有人翻譯爲“裝填因子”)。

查找性能分析

散列表的查找過程基本上和造表過程相同。一些關鍵碼可通過散列函數轉換的地址直接找到,另一些關鍵碼在散列函數得到的地址上產生了衝突,需要按處理衝突的方法進行查找。

對散列表查找效率的量度,依然用平均查找長度來衡量,散列表的平均查找長度是裝填因子α的函數,只是不同處理衝突的方法有不同的函數。
查找過程中,關鍵碼的比較次數,取決於產生衝突的多少,產生的衝突少,查找效率就高,產生的衝突多,查找效率就低。

因此,影響產生衝突多少的因素,也就是影響查找效率的因素。影響產生衝突多少有以下三個因素:

  1. 散列函數是否均勻;
  2. 處理衝突的方法;
  3. 散列表的裝填因子。

3. 散列函數

散列函數,也叫哈希函數、或者hash算法,其實也不一定是數學函數。散列技術是在記錄的存儲位置和它的關鍵字之間建立一個明確的對應關係f 函數,使得每個關鍵字 key 對應一個存儲位置 f(key) 且這個位置是唯一的,使用散列技術非常適合1對1查找。

定義:

  • 它是把任意長度的輸入(又叫做預映射, pre-image),通過散列算法,變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,而不可能從散列值來唯一的確定輸入值。
    簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。

  • 哈希算法將任意長度的二進制值映射爲較短的固定長度的二進制值,這個小的二進制值稱爲哈希值。哈希值是一段數據唯一且極其緊湊的數值表示形式。

MD4
MD4(RFC 1320)是 MIT 的 Ronald L. Rivest 在 1990 年設計的,MD 是 Message Digest 的縮寫。它適用在32位字長的處理器上用高速軟件實現–它是基於 32 位操作數的位操作來實現的。

MD5
MD5(RFC 1321)是 Rivest 於1991年對MD4的改進版本。它對輸入仍以512位分組,其輸出是4個32位字的級聯,與 MD4 相同。MD5比MD4來得複雜,並且速度較之要慢一點,但更安全,在抗分析和抗差分方面表現更好
  
SHA-1 及其他
SHA1是由NIST NSA設計爲同DSA一起使用的,它對長度小於264的輸入,產生長度爲160bit的散列值,因此抗窮舉(brute-force)性更好。SHA-1 設計時基於和MD4相同原理,並且模仿了該算法。

散列函數優缺點

散列函數優點

  1. 散列函數能使對一個數據序列的訪問過程更加迅速有效,通過散列函數,數據元素將被更快地定位。
    因爲散列法將元素特徵轉變爲數組下標。

散列函數缺點
散列函數有自身的缺點:
2. 會存在關鍵字重複的問題,比如說男女爲關鍵字的時候就不合適了。
3. 同樣不適合查找範圍的,比如說查找18-20歲之間的同學。

散列函數特性

它有兩個重要的特性需要我們重視,碰撞和抗篡改。
1. 抗篡改

  1. 從hash值不可以反向推導出原始的數據。這個從上面MD5的例子裏可以明確看到,經過映射後的數據和原始數據沒有對應關係

  2. 輸入數據的微小變化會得到完全不同的hash值,相同的數據會得到相同的值,如下:

輸入:
    System.out.println("內容的hash值 =".hashCode());
    String string2 = "內容的hash值 =";
    System.out.println( string2.hashCode());
    System.out.println("內容的hbsh值 =".hashCode());
    
輸出:
	627393883
	627393883
	656023034

2. 碰撞
任一hash函數都會出現哈希衝突,在後面會專門講解哈希衝突。

散列函數選取

hash函數有很多種,開發中經常使用的MD5和SHA都是歷史悠久的Hash算法。
優秀的散列函數的選取原則需要考慮各種因素,其常見的因素有如下幾種:

  1. 關鍵碼長度,關鍵碼長也能快速計算出哈希值
  2. 散列表大小
  3. 關鍵碼分佈均勻,儘量讓不同的關鍵碼有不同的函數散列值
  4. 記錄的檢索頻率
  5. 運算儘可能簡單,執行的效率要高
  6. 函數的值域必須在散列表範圍內
  7. hash算法的哈希碰撞率要低

“好的散列函數 = 計算簡單 + 分佈均勻”。
其中計算簡單指的是散列函數的計算時間不應該超過其他查找技術與關鍵字比較的時間,而分佈均勻指的是散列地址分佈均勻。

散列函數示例

我們假設處理的是值爲整型的關鍵碼,這樣我們就可以建立一種關鍵碼與正整數之間的一一對應關係,從而把該關鍵碼的檢索轉化爲對與其對應的正整數的檢索。同時,進一步假定散列函數的值落在0到M-1之間。

除了MD5和SHA等方案,再次介紹幾種其他的散列函數。 假設有一哈希表長度11,有如下數據 26 、5、14、35、29、3、9、16

1. 除留餘數法
取關鍵字X被某個不大於散列表表長m的數p除後所得的餘數爲散列地址。即 H(key) = key MOD p, p<=m。除餘法幾乎是最簡單的散列方法,不僅可以對關鍵字直接取模,也可在摺疊、平方取中等運算之後取模。

對p的選擇很重要,一般取素數或m,若p選的不好,容易產生同義詞。

示例:如函數H(key) = key % 11
在這裏插入圖片描述

2. 乘餘取整法
一種乘法運算,此方法先讓關鍵碼key乘上一個常數A (0< A < 1),提取乘積的小數部分。然後,再用整數n乘以這個值,對結果向下取整,把它做爲散列的地址。散列函數爲: hash ( key ) = _LOW( n × ( A × key % 1 ) )
其中,“A × key % 1”表示取 A × key 小數部分,即: A × key % 1 = A × key - _LOW(A × key),而_LOW(X)是表示對X取下整。

3. 平方取中法
由於整數相除的運行速度通常比相乘要慢,所以有意識地避免使用除餘法運算可以提高散列算法的運行時間。

平方取中法的具體實現是:先通過求關鍵碼的平方值,從而擴大相近數的差別,然後根據表長度取中間的幾位數(往往取二進制的比特位)作爲散列函數值。因爲一個乘積的中間幾位數與乘數的每一數位都相關,所以由此產生的散列地址較爲均勻。

關鍵字 關鍵字的平方 哈希函數值
1234 1522756 227
2143 4592449 924
4132 17073424 734
3214 10329796 297

這種方法適用於不知道關鍵字的分佈,且數值的位數又不是很大的情況

4. 數字分析法
假設關鍵字集合中的每個關鍵字都是由 s 位數字組成 (u1, u2, …, us),分析關鍵字集中的全體,並從中提取分佈均勻的若干位或它們的組合作爲地址。數字分析法是取數據元素關鍵字中某些取值較均勻的數字位作爲哈希地址的方法。

即當關鍵字的位數很多時,可以通過對關鍵字的各位進行分析,丟掉分佈不均勻的位,作爲哈希值。它只適合於所有關鍵字值已知的情況。通過分析分佈情況把關鍵字取值區間轉化爲一個較小的關鍵字取值區間。

舉個例子:要構造一個數據元素個數n=80,哈希長度m=100的哈希表。不失一般性,我們這裏只給出其中8個關鍵字進行分析,8個關鍵字如下所示:
K1=61317602
K2=61326875
K3=62739628
K4=61343634
K5=62706815
K6=62774638
K7=61381262
K8=61394220

分析上述8個關鍵字可知,關鍵字從左到右的第1、2、3、6位取值比較集中,不宜作爲哈希地址,剩餘的第4、5、7、8位取值較均勻,可選取其中的兩位作爲哈希地址。設選取最後兩位作爲哈希地址,則這8個關鍵字的哈希地址分別爲:2,75,28,34,15,38,62,20。

同樣的案例:比如一組員工的出生年月日,這時我們發現出生年月日的前幾位數字大體相 同,這樣的話,出現衝突的機率就會很大,但是我們發現年月日的後幾位表示月份和具體日期的數字差別很大,如果用後面的數字來構成散列地址,則衝突的機率會 明顯降低。

此法適於:能預先估計出全體關鍵字的每一位上各種數字出現的頻度。
通過數字分析法就是找出數字的規律,儘可能利用這些數據來構造衝突機率較低的散列地址。

5. 摺疊法
有時關鍵碼所含的位數很多,採用平方取中法計算太複雜,則可將關鍵碼分割成位數相同的幾部分(最後一部分的位數可以不同),然後取這幾部分的疊加和(捨去進位)作爲散列地址,這方法稱爲摺疊法。類型分爲兩種:

  1. 移位疊加:將分割後的幾部分低位對齊相加。
  2. 邊界疊加:從一端沿分割界來回摺疊,然後對齊相加。

將關鍵字分割成位數相同的幾部分,最後一部分位數可以不同,然後取這幾部分的疊加和(去除進位)作爲散列地址。

6. 直接尋址法
取關鍵字或關鍵字的某個線性函數值爲散列地址。即H(key)=key或H(key) = a?key + b,其中a和b爲常數(這種散列函數叫做自身函數)

7. 隨機數法
選擇一隨機函數,取關鍵字的隨機值作爲散列地址,通常用於關鍵字長度不同的場合。

f(key) = random(key)。這裏的random是隨機函數,當關鍵字的長度不等時,採用這個方法構造散列函數是比較合適的

8. 平方散列法
乘法的運算要比除法來得省時(對現在的CPU來說,估計我們感覺不出來),所以我們考慮把除法換成乘法和一個位移操作。
公式: index = (Key* Key) >> 28 (右移,除以2^28。記法:左移變大,是乘。右移變小,是除。)
如果數值分配比較均勻的話這種方法能得到不錯的結果。

9.位運算法
這類型Hash函數通過利用各種位運算(常見的是移位和異或)來充分的混合輸入元素
如公式:H(key)=Key>>14;

10. 斐波那契(Fibonacci)散列法
平方散列法的缺點是顯而易見的,所以我們能不能找出一個理想的乘數,而不是拿value本身當作乘數呢?答案是肯定的。其公式爲: H(key)= (Key* X) >> 28,X有如下幾種理想數選擇:

  1. 對於16位整數而言,這個乘數是40503。
  2. 對於32位整數而言,這個乘數是2654435769。
  3. 對於64位整數而言,這個乘數是11400714819323198485。

這幾個“理想乘數”是如何得出來的呢?這跟一個法則有關,叫黃金分割法則,而描述黃金分割法則的最經典表達式無疑就是著名的斐波那契數列,即如此形式的序列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946,…。

4. Hash衝突

哈希算法的實質是對原始數據的有損壓縮,有損壓縮後的固定字長用作唯一標識原始數據。若不同的原始數據被有損壓縮後產生了相同的結果,該現象稱爲哈希碰撞。也叫作哈希衝突,Hash Collision

由於hash的原理是將輸入空間的值映射成hash空間內,而hash值的空間遠小於輸入的空間。根據抽屜原理,一定會存在不同的輸入被映射成相同輸出的情況。

換一種理解即哈希函數可能對於不相等的關鍵碼計算出相同的散列地址,即key1≠key2,即hash(key1)=hash(key2),我們稱該現象爲衝突(collision),也即hash碰撞。發生衝突的兩個關鍵碼稱爲該散列函數的同義詞,一個好的hash算法,就需要這種衝突的概率儘可能小。

同義詞
兩個元素通過散列函數H(key)得到的散列值相同,那麼這兩個元素稱爲“同義詞”。因爲hash是一種壓縮映射,所以當負載因子足夠大是同義詞一定會存在。

如何解決Hash衝突

雖然任意兩個不同的數據塊,其hash值相同的可能性極小,且對於一個給定的數據塊,找到和它hash值相同的數據塊極爲困難。
但是在實際應用中,我們必須考慮在衝突發生時的處理辦法,因爲每種hash 函數都存在hash碰撞的可能。

解決衝突是一個複雜問題。衝突主要取決於:

  • 1)散列函數,一個好的散列函數的值應儘可能平均分佈。
  • 2)處理衝突方法。
  • 3)負載因子的大小。太大不一定就好,而且浪費空間嚴重,負載因子和散列函數是聯動的。

常見的兩種處理方案是是開散列方法( open hashing,也稱爲拉鍊法,separate chaining )閉散列方法( closed hashing,也稱爲開地址方法,open addressing )
不同之處在於:

  1. 開散列法把發生衝突的關鍵碼存儲在散列表主表之外
  2. 閉散列法把發生衝突的關鍵碼存儲在表中另一個槽內。

開散列方法(拉鍊法)

添加一個元素的時候,首先計算元素key的hash值,確定插入數組中的位置。如果當前位置下沒有數據,則直接添加到當前位置。當遇到衝突的時候,添加到同一個hash值的元素後面,行成一個鏈表。這個鏈表的特點是同一個鏈表上的Hash值相同。

開散列方法的一種簡單形式是把散列表中的每個槽定義爲一個鏈表的表頭。散列到一個特定槽的所有記錄都放到這個槽的鏈表中。

例如如圖,將12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34幾個數據存放到有12個槽的散列表中,通過取餘法散列函數
h(K) = K mod 12,得到的餘數就是每個數據的儲存地址,如圖下:
在這裏插入圖片描述

在JDK1.8中HashMap就是使用的拉鍊法解決衝突的,當鏈表上數據超過8條時,使用了紅黑樹進行了優化

涉及到紅黑樹:

閉散列方法(開放地址法)

開放地址法是指大小爲 M 的數組保存 N 個鍵值對,其中 M > N。我們需要依靠數組中的空位解決碰撞衝突。基於這種策略的所有方法被統稱爲“開放地址”哈希表。

簡單來說就是:一旦發生衝突,就去尋找下 一個空的散列表地址,只要散列表足夠大,空的散列地址總能找到。
下面介紹幾種閉散列方法中的幾種常見構造法:

1. 線性探測法
線性探測法,就是比較常用的一種“開放地址”哈希表的一種實現方式。線性探測法的核心思想是當衝突發生時,順序查看錶中下一單元,直到找出一個空單元或查遍全表。

線性探測法的數學描述是:h(k, i) = (h(k, 0) + i) mod m,i表示當前進行的是第幾輪探查。i=1時,即是探查h(k, 0)的下一個;i=2,即是再下一個。這個方法是簡單地向下探查。mod m表示:到達了表的底下之後,回到頂端從頭開始。

後面舉例假定:一組關鍵碼爲(26,36,38,44,15,68,12,06,51),散列表長度M= 13,使用取餘法

將散列表看成是一個環形表,若在基地址d(即h(K)=d)發生衝突,則依次探查下述地址單元:d+1,d+2,…,M-1,0,1,…,d-1直到找到一個空閒地址或查找到關鍵碼爲key的結點爲止。當然,若沿着該探查序列檢索一遍之後,又回到了地址d,則無論是做插入操作還是做檢索操作,都意味着失敗。

示例:用線性探測法來構造散列表,設定構造函數H(key)=Key%13,那麼散列表如下:
根據線性檢測,當計算12是地址與38衝突,往後面找…0,1,此時1爲空插入數據12。 當計算51時與38衝突,往後找…0,1,2,3,4,此時4位置空閒插入數據51。
在這裏插入圖片描述

2. 二次探測(Quadratic probing)
二次探查法的基本思想是:生成的後繼散列地址不是連續的,而是跳躍式的,以便爲後續數據元素留下空間從而減少聚集。如果有多個同義詞,那麼他們是對稱分佈在原始H(Key)兩側的

示例:用二次探測法來構造散列表,設定構造函數H(key)=Key%13,那麼散列表如下:
在這裏插入圖片描述
當計算h(12)=12時已存在38衝突,此時index=0已有數據26,查找0相對12的對稱點index=11空閒,將12填入index=11。

當計算h(51)=12時,衝突。查index=0衝突,index=11衝突,index=1時槽空閒,將51填入index=1。

3. 隨機探測(Double hashing)
在探查序列中隨機地從未訪問過的槽中選擇下一個位置,即探查序列應當是散列表位置的一個隨機排列。
我們可以做一些類似於僞隨機探查( pseudo-random probing )的事情。在僞隨機探查中,探查序列中的第i個槽是h(Key) = (Key+ ri) mod M,其中ri是1到M - 1之間數的“隨機”數序列。所有插入和檢索都使用相同的“隨機”數。

示例:用隨機測法來構造散列表, 僞隨機數列爲2,5,9… , 設定構造函數H(key)=Key%13,那麼散列表如下:
在這裏插入圖片描述
計算H(12)=12衝突,此時H(12+2)=1可填充
計算H(51)=12衝突,此時H(51+2)=1衝突,繼續H(51+5)=4槽空閒可填充

4. 雙散列探查法
僞隨機探查和二次探查都能消除基本聚集——即基地址不同的關鍵碼,其探查序列的某些段重疊在一起——的問題。

同時準備多個散列函數,當第一個散列函數發生衝突的時候可以用備選的散列函數進行計算。

雙散列探查法利用第二個散列函數作爲常數,每次跳過常數項,做線性探查。雙散列函數法:在位置d衝突後,再次使用另一個散列函數產生一個與散列表桶容量m互質的數c,依次試探(d+n*c)%m,使探查序列跳躍式分佈。

二級聚集
如果兩個關鍵碼散列到同一個基地址,那麼採用這兩種方法還是得到同樣的探查序列,仍然會產生聚集。這個問題稱爲二級聚集( secondary clustering )。

產生二級聚集的原因是因爲僞隨機探查和二次探查產生的探查序列只是基地址的函數,而不是原來關鍵碼值的函數。

爲了避免二級聚集,我們需要使得探查序列是原來關鍵碼值的函數,而不是基位置的函數。

公共溢出區法

假設關鍵字集合爲{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},同樣使用除留餘數法求散列表,如下圖所示:
在這裏插入圖片描述
沒有衝突的元素放在左邊的表,有衝突的元素,將多餘的元素放在右邊的那個表。

5. Hash的應用

hash算法常用於數據查找、信息加密、數據校驗、負載均衡。主要用於信息安全領域中,通過hash加密算法把一些不同長度的信息轉化成雜亂的128位的編碼,這些編碼值叫做HASH值. 也可以說,Hash就是找到一種數據內容和數據存放地址之間的映射關係。

  • 單向密碼
    它是一種單向密碼體制,即它是一個從明文到密文的不可逆的映射,只有加密過程,沒有解密過程。同時,哈希函數可以將任意長度的輸入經過變化以後得到固定長度的輸出。哈希函數的這種單向特徵和輸出數據長度固定的特徵使得它可以生成消息或者數據。

  • 抗篡改
    對於一個數據塊,哪怕只改動其一個比特位,其hash值的改動也會非常大。
    哈希函數可以將任意長度的輸入經過變化以後得到固定長度的輸出。如果輸入數據中有變化,則哈希值也會發生變化,哪怕只改動其一個比特位,其hash值的改動也會非常大。

    哈希的這種特性可用於許多操作,包括身份驗證和數字簽名,也稱爲“消息摘要”。

不同的應用對Hash函數有着不同的要求;比如,用於加密的Hash函數主要考慮它和單項函數的差距,而用於查找的Hash函數主要考慮它映射到小範圍的衝突率。

數據校驗

我們比較熟悉的校驗算法有奇偶校驗和CRC校驗,這2種校驗並沒有抗數據篡改的能力,它們一定程度上能檢測並糾正數據傳輸中的信道誤碼,但卻不能防止對數據的惡意破壞。

MD5 Hash算法的"數字指紋"特性,使它成爲目前應用最廣泛的一種文件完整性校驗和(Checksum)算法,不少Unix系統有提供計算md5 checksum的命令。

版權校驗

版權校驗在數據校驗方面的另一個應用場景就是版權的保護或者違禁信息的打擊,比如某個小視頻,第一個用戶上傳的時候,我們認爲是版權所有者,計算一個hash值存下來。當第二個用戶上傳的時候,同樣計算hash值,如果hash值一樣的話,就算同一個文件。

這種方案其實也給用戶傳播違禁文件提高了一些門檻,不是簡單的換一個名字或者改一下後綴名就可以躲避掉打擊了。(當然這種方式也是可以繞過的,圖片的你隨便改一下顏色,視頻去掉一幀就又是完全不同的hash值了。

另外我們在社區裏,也會遇到玩家重複上傳同一張圖片或者視頻的情況,使用這種校驗的方式,可以有效減少cos服務的存儲空間。

大文件分塊校驗

在p2p網絡中會把一個大文件拆分成很多小的數據各自傳輸。這樣的好處是如果某個小的數據塊在傳輸過程中損壞了,只要重新下載這個塊就好。爲了確保每一個小的數據塊都是發佈者自己傳輸的,我們可以對每一個小的數據塊都進行一個hash的計算,維護一個hash List,在收到所有數據以後,我們對於這個hash List裏的每一塊進行遍歷比對。

這裏有一個優化點是如果文件分塊特別多的時候,如果遍歷對比就會效率比較低。可以把所有分塊的hash值組合成一個大的字符串,對於這個字符串再做一次Hash運算,得到最終的hash(Root hash)。在實際的校驗中,我們只需要拿到了正確的Root hash,即可校驗Hash List,也就可以校驗每一個數據塊了。
在這裏插入圖片描述

數字簽名

Hash 算法也是現代密碼體系中的一個重要組成部分。由於非對稱算法的運算速度較慢,所以在數字簽名協議中,單向散列函數扮演了一個重要的角色。對 Hash 值,又稱"數字摘要"進行數字簽名,在統計上可以認爲與對文件本身進行數字簽名是等效的。通過對Hash值得校驗能認爲是對文本信息的校驗。
在這裏插入圖片描述

簽名原理
MD5-Hash-文件的數字文摘通過Hash函數計算得到。不管文件長度如何,它的Hash函數計算結果是一個固定長度的數字。與加密算法不 同,這一個Hash算法是一個不可逆的單向函數。採用安全性高的Hash算法,如MD5、SHA時,兩個不同的文件幾乎不可能得到相同的Hash結果。因 此,一旦文件被修改,就可檢測出來。

信息加密

不要以爲用MD5加密數據後,你的數據就安全了。有這麼一個牛逼網站https://www.cmd5.com/,有極大可能可以反向查詢出你的原始加密內容。該網站的官網介紹是這樣的:

	本站針對md5、sha1等全球通用公開的加密算法進行反向查詢,通過窮舉字符組合的方式,創建了明文密文對應查詢數據庫,創建的記錄約90萬億條,佔用硬盤超過500TB,查詢成功率95%以上,很多複雜密文只有本站纔可查詢。已穩定運行十餘年,國內外享有盛譽.

在這裏插入圖片描述
在這裏插入圖片描述
一般這種MD解密網站對於複雜內容的解密都是需要收費的。

爲了防止信息被反向查詢,一般針對這種問題,我們的解決之道就是引入salt(加鹽),即利用特殊字符(鹽)和用戶的輸入合在一起組成新的字符串進行加密。通過這樣的方式,增加了反向查詢的複雜度。

鑑權協議

鑑權協議又被稱作挑戰–認證模式:在傳輸信道是可被偵聽,但不可被篡改的情況下,這是一種簡單而安全的方法。

負載均衡

在開發大數據用戶量應用時,都會使用分庫分表,針對用戶的openid進行hashtime33取模,就可以得到對應的用戶分庫分表的節點了。也就是用戶太多了,分多個表存儲它們的數據。
在這裏插入圖片描述
如上假設應用設計了10張表,openid計算後的hash值取模10,得到對應的分表,在進行後續處理就好。對於一般的活動或者系統,我們一般設置10張表或者100張表就好。

假設我們活動初始分表了10張,運營一段時間以後發現需要10張不夠,需要改到100張。這個時候我們如果直接擴容的話,那麼所有的數據都需要重新計算Hash值,大量的數據都需要進行遷移。如果更新的是緩存的邏輯,則會導致大量緩存失效,發生雪崩效應,導致數據庫異常。造成這種問題的原因是hash算法本身的緣故,只要是取模算法進行處理,則無法避免這種情況。針對這種問題,我們就需要利用一致性hash進行相應的處理了擴容問題了。

一致性hash的基本原理是將輸入的值hash後,對結果的hash值進行2^32取模,這裏和普通的hash取模算法不一樣的點是在一致性hash算法裏將取模的結果映射到一個環上。將緩存服務器與被緩存對象都映射到hash環上以後,從被緩存對象的位置出發,沿順時針方向遇到的第一個服務器,就是當前對象將要緩存於的服務器,由於被緩存對象與服務器hash後的值是固定的,所以,在服務器不變的情況下,一個openid必定會被緩存到固定的服務器上,那麼,當下次想要訪問這個用戶的數據時,只要再次使用相同的算法進行計算,即可算出這個用戶的數據被緩存在哪個服務器上,直接去對應的服務器查找對應的數據即可。這裏的邏輯其實和直接取模的是一樣的。如下圖所示:
在這裏插入圖片描述
初始情況如下:用戶1的數據在服務器A裏,用戶2、3的數據存在服務器C裏,用戶4的數據存儲在服務器B裏

下面我們來看一下當服務器數量發生變化的時候,相應影響的數據情況:

  • 服務器縮容
    在這裏插入圖片描述
    服務器B發生了故障,進行剔除後,只有用戶4的數據發生了異常。這個時候我們需要繼續按照順時針的方案,把緩存的數據放在用戶A上面。

  • 服務器擴容
    同樣的,我們進行了服務器擴容以後,新增了一臺服務器D,位置落在用戶2和3之間。按照順時針原則,用戶2依然訪問的是服務器C的數據,而用戶3順時針查詢後,發現最近的服務器是D,後續數據就會存儲到d上面。

博客書寫不易,您的點贊收藏是我前進的動力,千萬別忘記點贊、 收藏 ^ _ ^ !

相關鏈接

  1. Android 史上最新最全的ADB及命令百科,沒有之一
  2. Room數據庫,用過你才知道好
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章