redis里面字典的實(shí)現(xiàn)

字典怀偷,又稱符號(hào)表宠叼,是保存鍵值對(duì)的抽象數(shù)據(jù)結(jié)構(gòu)鄙皇。很多語言都內(nèi)置字典這種常用的數(shù)據(jù)結(jié)構(gòu)芜赌,但是C語言沒有內(nèi)置,所以redis自己來實(shí)現(xiàn)了字典伴逸。

redis中的字典由dict.h/dict結(jié)構(gòu)來定義:

typedef struct dict {
    dictType *type;     //類型特定函數(shù)
    void *privdata;     //私有數(shù)據(jù)
    dictht ht[2];       //哈希表
    int rehashdx;      //rehash 索引,當(dāng) rehash 不在進(jìn)行時(shí),值為-1
} dict;
  1. type和privdata是針對(duì)不同類型的鍵值對(duì)缠沈,為創(chuàng)建多態(tài)字典而設(shè)置的
    type是指向dictType結(jié)構(gòu)的指針,每個(gè)dictType結(jié)構(gòu)保存了一簇操作特定類型鍵值對(duì)的函數(shù)错蝴,redis會(huì)為用途不同的字典設(shè)置不同的特定處理函數(shù)洲愤。
    privdata則保存了傳給那些特定處理函數(shù)的可選參數(shù)的信息。

  2. ht屬性包含兩個(gè)數(shù)組顷锰,這兩個(gè)數(shù)組都是一個(gè)dictht哈希表柬赐,一般情況下字典只使用ht[0]這個(gè)哈希表,ht[1]只會(huì)在對(duì)哈希表進(jìn)行rehash時(shí)才會(huì)使用官紫。

  3. rehashdx記錄了rehash當(dāng)前的進(jìn)度肛宋,如果沒有進(jìn)行rehash,則 rehashdx值為-1

可以看到束世,字典dict結(jié)構(gòu)中悼吱,最重要的就是dictht 這種類型的ht哈希表(平時(shí)都是ht[0]在起作用),哈希表的數(shù)據(jù)結(jié)構(gòu)dictht定義為:

typedef struct dictht {
    dictEntry **table;      //哈希表數(shù)組
    unsigned long size;     //哈希表大小
    unsigned long sizemask; //用于計(jì)算索引值,
                            //總是等于 size - 1
    unsigned long used;     //哈希表已有節(jié)點(diǎn)數(shù)量
}  dictht;
  1. table屬性是一個(gè)數(shù)組良狈,數(shù)組中每個(gè)元素都是指向一個(gè)dictEntry結(jié)構(gòu)的指針,每個(gè)dictEntry結(jié)構(gòu)保存著一個(gè)鍵值對(duì)笨枯。
  2. size屬性記錄了hash表的大小薪丁,也就是table這個(gè)數(shù)組的大小遇西。
  3. sizemask屬性和哈希值共同決定了一個(gè)鍵應(yīng)該被放到數(shù)組的哪個(gè)索引上。

可以看到严嗜,哈希表結(jié)構(gòu)dictht中粱檀,最重要的就是table中元素指向的哈希表節(jié)點(diǎn)dictEntry這種鍵值對(duì)結(jié)構(gòu),hash表節(jié)點(diǎn)dictEntry的結(jié)構(gòu)定義為:

typedef struct dictEntry {
    void *key;          //鍵
    union {             //值
        void *val;
        uint_64 u64;
        int64_t s64;
    } v;                    
    sturct dictEntry *next; //指向下個(gè)哈希表節(jié)點(diǎn),形成鏈表
} dictEntry;
  1. v屬性保存著鍵值對(duì)的值漫玄,其中的鍵值可以是指針茄蚯,或者是uint_64 類型,也可能是int64_t 類型睦优。
  2. next屬性是指向另一個(gè)哈希表節(jié)點(diǎn)的指針渗常,這個(gè)指針把多個(gè)哈希值相同的節(jié)點(diǎn)連接在一起,來解決hash碰撞問題汗盘。

下圖是普通狀態(tài)(不是在rehash狀態(tài))下的字典:

Paste_Image.png

在這個(gè)字典中皱碘,ht[0]這個(gè)hashtable中,table數(shù)組中有4個(gè)哈希節(jié)點(diǎn)(ditcEntry)隐孽,其中哈希值為0和3的節(jié)點(diǎn)無鍵值對(duì)癌椿,哈希值為1的節(jié)點(diǎn)有兩個(gè)鍵值對(duì),用next指針相連菱阵,哈希值為2的節(jié)點(diǎn)有一個(gè)鍵值對(duì)踢俄。
還可以看到,字典在普通狀態(tài)下晴及,ht[1]這個(gè)hashtable里面都办,table這個(gè)數(shù)組沒有元素,為空抗俄。

哈希算法

當(dāng)要將一個(gè)新的鍵值對(duì)添加到字典里面時(shí)脆丁,程序會(huì)根據(jù)鍵計(jì)算出哈希值和索引值,然后再根據(jù)索引值將新鍵值對(duì)放到哈希表數(shù)組指定的索引上动雹。

解決鍵沖突

可能存在這樣一種情況:兩個(gè)不同的鍵值對(duì)槽卫,計(jì)算出的哈希值和索引值是一樣的。這就叫hash碰撞胰蝠。怎么解決hash碰撞的問題呢歼培??我們想到了每個(gè)hash節(jié)點(diǎn)都有一個(gè)next指針茸塞,借助于這個(gè)next指針躲庄,我們可以將哈希值一樣的節(jié)點(diǎn)串起來,形成一個(gè)單鏈表钾虐。

rehash

隨著鍵值對(duì)的逐漸增多或減少噪窘,為了讓哈希表的負(fù)載因子維持在一個(gè)合理的范圍之內(nèi),當(dāng)哈希表保存的鍵值對(duì)太多或者太少時(shí)效扫,程序需要對(duì)哈希表進(jìn)行相應(yīng)的擴(kuò)展或者收縮倔监,這個(gè)過程叫做rehash直砂。

Redis 對(duì)字典的哈希表執(zhí)行 rehash 的步驟如下:

  1. 為字典的 ht[1] 哈希表分配空間,空間的大小取決于要執(zhí)行的操作,以及ht[0]當(dāng)前包含的鍵值對(duì)數(shù)量( used 屬性值):
    如果執(zhí)行的是擴(kuò)展操作,那么 ht[1] 的大小為第一個(gè)大于等于ht[0].used*2的 $2^n$ .
    如果執(zhí)行的是收縮操作,那么ht[1]的大小為打一個(gè)大于等于ht[0].used 的$2^n$.
  2. 將保存在 ht[0] 中所有鍵值對(duì) rehash 到 ht[1] 上面: 任何事指的是重新計(jì)算鍵的哈希值和索引值,然后將鍵值對(duì)放到 ht[1] 哈希表的指定位置.
  3. 當(dāng) ht[0] 包含的所有鍵值對(duì)都遷移到了ht[1]之后, 釋放 ht[0], 再將 ht[1] 設(shè)置為 ht[0],并在 ht[1] 后面創(chuàng)建一個(gè)空白的哈希表.

舉個(gè)例子,假設(shè)程序要對(duì)下圖的 `ht[0] 進(jìn)行擴(kuò)展操作


ht[0].used 當(dāng)前值為4 , $2^3$ 恰好是第一個(gè)大于等于 4*2 的值,所以 ht1 哈希表的大小設(shè)置為8,下圖展示了 ht1 分配了空間之后字典的樣子.
此處輸入圖片的描述

將 ht[0] 包含的四個(gè)鍵值對(duì) rehash 到 ht1, 圖下圖所示:

釋放 ht[0], 將 ht1 設(shè)置為 ht[0]. 再分配一個(gè)空哈希表. 哈希表的大小由原來的4 擴(kuò)展至8.

漸進(jìn)式rehash

拓展或者收縮哈希表需要將ht[0]里所有的鍵值對(duì)重新hash到ht[1]中,但是這個(gè)rehash動(dòng)作并不是一次性集中式完成的浩习。而是分多次漸進(jìn)式完成的静暂。原因也很清楚,就是鍵值對(duì)過多時(shí)谱秽,一次性rehash肯定會(huì)消耗服務(wù)器大量的計(jì)算資源洽蛀,可能會(huì)對(duì)服務(wù)器的性能造成影響。

以下是漸進(jìn)式rehash的步驟:

  1. 為ht[1]分配空間
  2. 在字典中維持一個(gè)索引計(jì)數(shù)器變量 rehashidx疟赊,將它的值設(shè)置為0郊供,表示rehash正式開始。
  3. 在rehash進(jìn)行期間听绳,每次對(duì)字典進(jìn)行增刪改查時(shí)颂碘,順帶將ht[0]在rehashidx索引上的所有鍵值對(duì)rehash到ht[1]中,同時(shí)將rehashidx增加1
  4. 隨著rehash的不斷進(jìn)行椅挣,最終在某個(gè)時(shí)間點(diǎn)上头岔,ht[0]上的所有鍵值對(duì)都rehash到ht[1]上了,這時(shí)將rehashidx屬性置為-1鼠证,表示rehash操作完成峡竣。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市量九,隨后出現(xiàn)的幾起案子适掰,更是在濱河造成了極大的恐慌,老刑警劉巖荠列,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件类浪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡肌似,警方通過查閱死者的電腦和手機(jī)费就,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來川队,“玉大人力细,你說我怎么就攤上這事」潭睿” “怎么了眠蚂?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斗躏。 經(jīng)常有香客問我逝慧,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任馋艺,我火速辦了婚禮栅干,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捐祠。我一直安慰自己,他們只是感情好桑李,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布踱蛀。 她就那樣靜靜地躺著,像睡著了一般贵白。 火紅的嫁衣襯著肌膚如雪率拒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天禁荒,我揣著相機(jī)與錄音猬膨,去河邊找鬼。 笑死呛伴,一個(gè)胖子當(dāng)著我的面吹牛勃痴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播热康,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼沛申,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了姐军?” 一聲冷哼從身側(cè)響起铁材,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奕锌,沒想到半個(gè)月后著觉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惊暴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年饼丘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缴守。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡葬毫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出屡穗,到底是詐尸還是另有隱情贴捡,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布村砂,位于F島的核電站烂斋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汛骂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一罕模、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧帘瞭,春花似錦淑掌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至媒殉,卻和暖如春担敌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背廷蓉。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國打工全封, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桃犬。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓刹悴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疫萤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子颂跨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容