redis源碼分析系列文章
面試官:說說Redis的Hash底層 我:......(來自閱文的面試題)
前言
大噶好,今天仍然是元氣滿滿的一天,拋開永遠寫不完的需求,拒絕要求賊變態的客戶,單純的學習技術,感受技術的魅力。(哈哈哈,皮一下很開森)
前面幾周我們一起看了Redis底層數據結構,如動態字符串SDS
,雙向鏈表Adlist
,字典Dict
,跳躍表
,如果有對Redis常見的類型或底層數據結構不明白的請看上面傳送門。
今天來說下set的底層實現整數集合
,如果有對set不明白的,常見的API使用這篇就不講了,看上面的傳送門哈。
整數集合概念
整數集合是Redis設計的一種底層結構,是set的底層實現,當集合中只包含整數值元素,並且這個集合元素數據不多時,會使用這種結構。但是如果不滿足剛纔的條件,會使用其他結構,這邊暫時不講哈。
下圖爲整數集合的實際組成,包括三個部分,分別是編碼格式encoding,包含元素數量length,保存元素的數組contents。(這邊只需要簡單看下,下面針對每個模塊詳細說明哈😝)
整數集合的實現
我們看下intset.h裏面關於整數集合的定義,上代碼哈:
編碼格式encoding
包括INTSET_ENC_INT16,INTSET_ENC_INT32,INTSET_ENC_INT64三種類型,其分別對應着不同的範圍,具體看上面代碼的註釋信息。
因爲插入的數據的大小是不一樣的,爲了儘可能的節約內存
(畢竟都是錢,平時要省着點用😭),所以我們需要使用不同的類型來存儲數據。
集合元素數量length
記錄了保存數據contents的長度,即有多少個元素。
保存元素的數組contents
真正存儲數據的地方,數組是按照從小到大
有序排序的,並且不包含任何重複項
(因爲set是不含重複項,所以其底層實現也是不含包含項的)。
整數集合升級過程(重點,手動標星)
上面的圖我們重新看下,編碼格式encoding爲INTSET_ENC_INT16,即每個數據佔16位。長度length爲4,即數組content裏面有四個元素,分別是1,2,3,4。如果我們要添加一個數字位40000,很明顯超過編碼格式爲INTSET_ENC_INT16的範圍-32,768~32,767,應該是編碼格式爲INTSET_ENC_INT32。那麼他是如何升級的呢,從INTSET_ENC_INT16升級到INTSET_ENC_INT32的呢?
1.瞭解舊的存儲格式
首先我們看下1,2,3,4這四個元素是如何存儲的。首先要知道一共有多少位,計算規則爲length*編碼格式的位數
,即4*16=64
。所以每個元素佔用了16位。
2.確定新的編碼格式
新的元素爲40000,已經超過了INTSET_ENC_INT16的範圍-32,768~32,767,所以新的編碼格式爲INTSET_ENC_INT32。
3.根據新的編碼格式新增內存
上面已經說明了編碼格式爲INTSET_ENC_INT32,計算規則爲length*編碼格式的位數
,即5*32=160
。所以新增的位數爲64-159。
4.根據編碼格式設置對應的值
從上面知道按照新的編碼格式,每個數據應該佔用32位,但是舊的編碼格式,每個數據佔用16位。所以我們從後面開始,每次獲取32位用來存儲數據。
這樣說太難懂了,看下圖☺。
首先,那最後32位,即128-159存儲40000。那麼第49-127是空着的。
接着,取空着的49-127最後的32位,即96到127這32位,用來存儲4。那麼之前4存儲的位置48-63
和49-127剩下的64-95
這兩部分組成了一個大部分,即48-95
,現在空着啦。
在接着在48-95這個大部分,再取後32位,即64-95,用來存儲3。那麼之前3存儲位置32-47
和48-95剩下的48-63
這兩部分組成了一個大部分,即32-63
,現在空着啦。
再接着,將32-63這個大部分,再取後32位,即還是32-63,用來存儲2。那麼之前2存儲位置16-31空着啦。
最後,將16-31和原來0-31合起來,存儲1。
至此,整個升級過程結束。整體來說,分爲3步,確定新的編碼格式,新增需要的內存空間,從後往前調整數據。
這邊有個小問題,爲啥要從後往前調整數據呢?
原因是如果從前往後,數據可能會覆蓋。也拿上面個例子來說,數據1在0-15位,數據2在16-31位,如果從前往後,我們知道新的編碼格式INTSET_ENC_INT32要求每個元素佔用32位,那麼數據1應該佔用0-31,這個時候數據2就被覆蓋了,以後就不知道數據2啦。
但是從後往前,因爲後面新增了一些內存,所以不會發生覆蓋現象。
升級的優點
節約內存
整數集合既可以讓集合保存三種不同類型的值,又可以確保升級操作只在有需要的時候進行,這樣就節省了內存。
不支持降級
一旦對數組進行升級,編碼就會一直保存升級後的狀態。即使後面把40000刪掉了,編碼格式還是不會將會INTSET_ENC_INT16。
整數集合的源碼分析
創建一個空集合 intsetnew
這個方法比較簡單,是初始化整數集合的步驟,即下圖部分。
主要的步驟是分配內存空間,設置默認編碼格式,以及初始化數組長度length。
添加元素並升級insetAdd流程圖(重點)
添加元素並升級insetAdd源碼分析
可以根據上面的流程圖,對照着下面的源碼分析,這邊就不寫啦哈。
結語
該篇主要講了Redis的SET數據類型的底層實現整數集合,先從整數集合是什麼,,剖析了其主要組成部分,進而通過多幅過程圖解釋了intset是如何升級的,最後結合源碼對整數集合進行描述,如創建過程,升級過程,中間穿插例子和過程圖。
如果覺得寫得還行,麻煩給個贊👍,您的認可纔是我寫作的動力!
如果覺得有說的不對的地方,歡迎評論指出。
好了,拜拜咯。