博客 / 詳情

返回

大軍閒聊 -- 聊一聊hash

我們熟悉的hash結構,首先是一個數組元素的哈希桶,比如下圖,是長度為4的哈希桶。

image.png

也就是説,當key通過hash計算後,對4進行取模,根據結果存放這個指定位置。比如取模後值為0,那就放第一個位置。

哈希桶的每個位置,保存的是entry的對象,這個entry對象包括key、value以及entry對象。

image.png

這個速度是非常快的,時間複雜度是O(1),所以map的性能還是不錯的。

如果模數相等呢?我們的entry對象中包括了entry對象,也就是説會通過鏈表,把相同模數的entry連接起來。

image.png

但是數據量越來越大的時候,就會發現一個問題,那就是相同的模數會越來越多,也就是説哈希表的衝突問題越來越嚴重,嚴重到影響到性能。

那怎麼辦呢?

常用的方法是通過rehash擴容來解決hash衝突,哈希桶數量的數量,讓entry存放於更多的哈希桶鍾。也就是上面的4變成8,變成16,這樣可以直接降低衝突的概率。

看起來很完美,除了併發可能帶來的死循環(參考HashMap源碼解析),依然有一個潛在的風險點。

正常rehash的時候,步驟是這樣的:

  1. 創建一個新的哈希桶,數量是之前的2倍。
  2. 把舊的數據重新計算存入新的哈希桶中。
  3. 刪除舊的哈希桶。

如果數據量很大的話,那整個過程就一直堵塞在第二個步驟中。

redis

redis也用了哈希桶,他是這麼解決的,把每一次的拷貝,都分攤在每一個請求中。

假設原長度為4,新長度為8。

image.png

那第一次處理請求的時候,就只處理1上面的entry鏈表。

image.png

此時4上面的entry鏈表還沒處理,當第二次請求的時候,就會處理。

image.png

通過分批處理,巧妙的把一次非常耗時的操作,分攤到一個個小的操作裏。(但是請求 的時候,還是要考慮兩個哈希桶存在的情況)。

JVM

分批操作,在JVM的G1收集器也有類似的手法。

redis是單線程,所以需要避免耗時的rehash的操作導致線程阻塞,而JVM回收垃圾的時候,會stop the world,也會阻塞應用,所以也要避免長時間的垃圾回收操作。

所以G1是會根據停頓時間來進行垃圾回收,有時候回收的垃圾,並不能釋放理想的內存空間,那就會分多次進行回收,最多8次,儘可能在減少停頓時間的情況下最大化的回收垃圾。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.