哈希表的實(shí)現(xiàn)

哈希表的實(shí)現(xiàn)

  • 哈希表可以認(rèn)為是redis最為重要的一個(gè)數(shù)據(jù)結(jié)構(gòu)
  • 實(shí)現(xiàn)了key,value的查找膛檀,保存所有的key等

1.基本數(shù)據(jù)結(jié)構(gòu)

typedef struct dictEntry {
    void *key;// 鍵
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v; // 值(可以有幾種不同類型)
    struct dictEntry *next;// 指向下一個(gè)哈希節(jié)點(diǎn)(形成鏈表)
} dictEntry;
  • 這個(gè)數(shù)據(jù)結(jié)構(gòu)可以認(rèn)為是hash表的一個(gè)元素芹缔,包含鍵值對(duì)枢步。用于鏈接法散列。是鏈接法散列中的鏈表部分祝峻。具體的第一個(gè)key,value 可以是聯(lián)合體里面的幾種類型
    最后一個(gè)是指向具有相同hash值的元素枯夜。
typedef struct dictType {
        unsigned int (*hashFunction)(const void *key);//哈希計(jì)算方法,返回整形變量
        void *(*keyDup)(void *privdata, const void *key);
        void *(*valDup)(void *privdata, const void *obj);
        int (*keyCompare)(void *privdata, const void *key1, const void *key2);
        void (*keyDestructor)(void *privdata, void *key);//key的析構(gòu)函數(shù)
        void (*valDestructor)(void *privdata, void *obj);//val的析構(gòu)函數(shù)
} dictType;
  • 這個(gè)數(shù)據(jù)結(jié)構(gòu)定義了一些在hash表上的一系列操作祷愉,結(jié)構(gòu)體里面都是函數(shù)指針窗宦,每個(gè)hash表里面都會(huì)有這個(gè)數(shù)據(jù)結(jié)構(gòu),對(duì)每個(gè)hash表進(jìn)行操作二鳄。
typedef struct dictht {
        dictEntry **table;//字典實(shí)體赴涵,使用的是數(shù)組形式,方便查找key
        unsigned long size;//字典大小
        unsigned long sizemask;//字典掩碼订讼,為size-1
        unsigned long used;//這個(gè)字典表中(數(shù)組中)髓窜,被使用的個(gè)數(shù)
} dictht;
  • 定義了hash表的形式,使用一個(gè)保存hash表元素地址的一個(gè)數(shù)組欺殿,還有存儲(chǔ)hash表的大小寄纵,大小掩碼,使用個(gè)數(shù)脖苏〕淌茫可以認(rèn)為是一個(gè)數(shù)組的擴(kuò)展。方便管理的數(shù)組棍潘。
typedef struct dict {
        dictType *type;//定義了對(duì)于hash表操作的一些列函數(shù)
        void *privdata;//私有的數(shù)據(jù)
        dictht ht[2];//兩個(gè)hash表恃鞋,一個(gè)新的一個(gè)舊的,可以在必要的時(shí)候進(jìn)行hash表的擴(kuò)張蜒谤,和hash表的rehash山宾。
        long rehashidx; /* rehashing not in progress if rehashidx == -1 *///rehash的標(biāo)志-1表示沒在rehash,可以根據(jù)這個(gè)值來取得兩張hash表哪個(gè)可以使用鳍徽。
        int iterators; /* number of iterators currently running *///這個(gè)hash表上迭代器的個(gè)數(shù)
} dict;
  • 對(duì)于兩個(gè)hash表的管理數(shù)據(jù)結(jié)構(gòu)资锰,所有的操作都是通過這個(gè)數(shù)據(jù)結(jié)構(gòu)來進(jìn)行的。包含hash元素的添加阶祭,刪除绷杜,查找直秆,擴(kuò)張,rehash鞭盟。
typedef struct dictIterator {
        dict *d;//管理hash表的數(shù)據(jù)機(jī)構(gòu)
        long index;//索引
        int table, safe;//表和安全性標(biāo)志
        dictEntry *entry, *nextEntry;//當(dāng)前元素和下一個(gè)元素
        /* unsafe iterator fingerprint for misuse detection. */
        long long fingerprint;//指紋
} dictIterator;
  • hash表上面的迭代器圾结,可以通過迭代器來訪問hash表中的元素。當(dāng)safe=1時(shí)候是安全的齿诉,可以對(duì)于hash表進(jìn)行刪除插入操作筝野。否則只能讀取hash表
static void _dictReset(dictht *ht)
  • 對(duì)于hash表進(jìn)行空的初始化,把hash表中的組數(shù)粤剧,大小都設(shè)置為默認(rèn)值歇竟。
dict *dictCreate(dictType *type, void *privDataPtr)
  • 創(chuàng)建一個(gè)管理hash表的數(shù)據(jù)結(jié)構(gòu),首先分配內(nèi)存抵恋,設(shè)置管理hash表數(shù)據(jù)結(jié)構(gòu)的hash類型焕议,私有數(shù)據(jù)佛猛,迭代器舶胀,最為主要的是初始化兩張hash表。
    這樣兩張hash表就創(chuàng)建成功了冬骚,但是現(xiàn)在里面并沒有任何元素世囊,也可以說這兩張表(兩個(gè)數(shù)組)內(nèi)容是hash元素指針别瞭,兩個(gè)數(shù)組里面并沒有開始分配內(nèi)存,只有數(shù)組名茸习。
  • 正真進(jìn)行分配內(nèi)存的時(shí)候(給dictht的table分配內(nèi)存)是在dict的擴(kuò)張時(shí)候畜隶。見下面的函數(shù)int dictExpand(dict *d, unsigned long size)
int _dictInit(dict *d, dictType *type,void *privDataPtr)

-根據(jù)參數(shù)對(duì)于管理兩個(gè)hash表的數(shù)據(jù)結(jié)構(gòu)進(jìn)行初始化,設(shè)置

int dictExpand(dict *d, unsigned long size)
  • 創(chuàng)建或者擴(kuò)張hash表号胚,其中size為hash表的大小籽慢,也就是dictht中table的大小,動(dòng)態(tài)分配內(nèi)存猫胁。
  • 創(chuàng)建hash表:table存儲(chǔ)的是hash元素值的指針箱亿,在這個(gè)時(shí)候會(huì)給table分配內(nèi)存,分配內(nèi)存的大小是大于size的2的指數(shù)倍弃秆。給的size大小需要檢查届惋,不能超過long_max值。同時(shí)這個(gè)值還不能和當(dāng)前hash表的大小一致菠赚,這樣沒什么意義脑豹。
  • 分配好table的大小之后,就可以把它賦值給管理hash表的數(shù)據(jù)結(jié)構(gòu)了衡查。管理數(shù)據(jù)結(jié)構(gòu)dict通過ht[2]來對(duì)這兩個(gè)hash表進(jìn)行管理瘩欺。
  • 這樣就給兩個(gè)hash表分配的內(nèi)存,其實(shí)這兩個(gè)hash表的指針是指向同一快內(nèi)存的。這只是在創(chuàng)建的時(shí)候兩張hash表是同一塊內(nèi)存俱饿。ht[0]=ht[1] if (ht[0].table=null)
  • Expend過程:上面的創(chuàng)建過程給兩張表指針指向同一塊內(nèi)存歌粥,其實(shí)如果兩個(gè)hash表已經(jīng)初始化了之后,就可以達(dá)到擴(kuò)展的過程拍埠。ht[0].table=ht[1].table if (ht[0].table=null)
  • 這語句的意思就是在擴(kuò)張的時(shí)候一個(gè)hash表已經(jīng)分配內(nèi)存了失驶,就會(huì)給另一張表分配內(nèi)存。并不會(huì)覆蓋原來有值的hash表枣购。
int dictRehash(dict *d, int n)
  • 重新hash嬉探,就是把hashtable[0](ht[0])里面的所有的鍵重新hash一下,然后把鍵值存儲(chǔ)在hashtable1里面坷虑,并且hashtable[0]還指向重新hash后的hash表甲馋。

  • 整個(gè)表都rehash完成后返回0,部分rehash返回1迄损。

  • 具體的過程是這樣的:

  • 首先在hashtable[0]里面找到所有非空的鍵值對(duì)(數(shù)組中查找到鏈表頭節(jié)點(diǎn)(頭指針)),然后對(duì)這個(gè)查找到的鏈表進(jìn)行遍歷账磺,鏈表中的每一個(gè)元素都是鍵值對(duì)芹敌。根據(jù)鏈表節(jié)點(diǎn)中的鍵key,找到在hashtable[1]數(shù)組中的索引位置垮抗,然后插入到這個(gè)索引所在的鏈表的表頭氏捞。

  • 這樣就可以完成一個(gè)字典實(shí)體的轉(zhuǎn)換,一只把整個(gè)鏈表遍歷完畢冒版。然后在把hashtable[0]數(shù)組中的所有非空元素都遍歷完畢液茎。
    最后實(shí)現(xiàn)了,hashtable[0]里面的所有元素辞嗡,轉(zhuǎn)換為hashtable[1]中的所有元素捆等。- 實(shí)現(xiàn)rehash的過程。注意每次最多遍歷n*10個(gè)元素续室。

  • 結(jié)束了上面的兩重遍歷之后栋烤,就可以檢查是否完成整個(gè)rehash的過程是否完成。會(huì)進(jìn)行一些列的釋放資源的操作挺狰,比如釋放hashtable[0]的內(nèi)存明郭,使得hashtable[1]和hashtable[0]指針互換。重置hashtable[1]

  • 從這里也可以看出來丰泊,主要的hash表還是hashtable[0],hashtable[1]只是起到了中間作用薯定。還有一點(diǎn)就是可能會(huì)存在部分rehash的過程⊥海可以把部分rehash的內(nèi)容话侄,存放在另一個(gè)臨時(shí)的hash表中。

  • 回過頭來看看苛败,rehash的過程是把一個(gè)hash表中所有元素拿出來之后满葛,按著某種方式對(duì)key進(jìn)行重新hash然后存放在其他的表中径簿。

long long timeInMilliseconds(void)
  • 獲取當(dāng)前時(shí)間的毫秒數(shù),使用到了gettimeofday函數(shù)
int dictRehashMilliseconds(dict *d, int ms)
  • 在ms毫秒之間進(jìn)行hash,也就是說控制rehash的時(shí)間超過ms毫秒嘀韧。只要rehash時(shí)間沒有超過ms毫秒篇亭,那么增加rehash的參數(shù)n大小,每次增加100锄贷,知道所有rehash時(shí)間超過ms毫秒.
  • 這是一種怎樣的控制啊译蒂,強(qiáng)制要求rehash超過一定的時(shí)間,我覺著可能是為了控制rehash的大小谊却。移動(dòng)元素的個(gè)數(shù)柔昼。考慮到rehash執(zhí)行的時(shí)間對(duì)于其他請(qǐng)求的影響炎辨。
static void _dictRehashStep(dict *d)
  • 當(dāng)沒有迭代器的時(shí)候會(huì)每次rehash10個(gè)元素(調(diào)用dictRehash(dict, 1) )捕透,逐漸的把hashtable[0]里面的元素遷移到hashtable[1]中。什么時(shí)候能夠知道完全rehash完畢呢碴萧? 根據(jù)dictRehash函數(shù)的返回值乙嘀。
static int _dictKeyIndex(dict *d, const void *key)
  • 查找key所在hashtable([0],[1])中的index,key存在返回所在數(shù)組的index破喻,否則返回-1虎谢。
  • 先根據(jù)key和整個(gè)hash表的hash計(jì)算方法,計(jì)算出key的hash碼曹质,也就是在hashtable中的索引婴噩,其實(shí)是這個(gè)key應(yīng)該在哪個(gè)位置上∮鸬拢肯定會(huì)有一個(gè)index几莽,其次如果這個(gè)index指向的鏈表中
  • 如果鏈表為空,那么返回hash碼玩般,否則需要遍歷這個(gè)鏈表查看是否存在與key對(duì)應(yīng)的元素银觅。最后如果正在rehash,或者rehash 并沒有完成(部分rehash),那么就需要坏为,把兩個(gè)表都遍歷究驴,否則只查找一個(gè)表(hashtable[0])
dictEntry *dictAddRaw(dict *d, void *key)
  • 添加只有key的字典元素到hashtable中,并返回可以設(shè)置value的字典元素指針匀伏。后來需要調(diào)用setvalue函數(shù)來進(jìn)行value的設(shè)置洒忧。
    這個(gè)函數(shù)會(huì)首先調(diào)用_dictKeyIndex查找到應(yīng)該在的index,當(dāng)然也可能會(huì)key已經(jīng)存在了够颠。然后根據(jù)是否部分rehash,來決定index是在哪一個(gè)表中熙侍。hashtable[0] or hashtable[1]
  • 然后創(chuàng)建字典元素實(shí)體,并設(shè)置key值,放在hashtable中蛉抓。
int dictAdd(dict *d, void *key, void *val)
  • 添加對(duì)key,value對(duì)到hashtable中庆尘,向字典中添加元素。
  • 會(huì)調(diào)用dictAddRaw添加元素的key
  • 然后調(diào)用dictSetVal設(shè)置key在hashtable中的實(shí)體的value值巷送。
dictEntry *dictFind(dict *d, const void *key)
  • 查找key所在的字典實(shí)體驶忌,返回字典實(shí)體的指針
  • 這個(gè)查找的過程和_dictKeyIndex類似,只是這里面要查找到具體的字典實(shí)體所在的內(nèi)存地址
  • 首先遍歷兩個(gè)hashtable(數(shù)組)笑跛,找到頭節(jié)點(diǎn)付魔,接著遍歷鏈表,找到和key相同的字典實(shí)體飞蹂,并返回地址几苍。找不到就會(huì)返回NULL
int dictReplace(dict *d, void *key, void *val)
  • 調(diào)用dictFind找到字典實(shí)體,調(diào)用dictSetVal設(shè)置字典的新值陈哑,然后釋放原來字典元素的內(nèi)容妻坝。
dictEntry *dictReplaceRaw(dict *d, void *key)
  • 查找key,如果存在則添加key,不存在怎返回NULL
static int dictGenericDelete(dict *d, const void *key, int nofree)
  • 查找并刪除一個(gè)key元素芥颈,
  • 根據(jù)兩張hashtable來進(jìn)行查找惠勒,找到一個(gè)頭節(jié)點(diǎn)之后,進(jìn)行遍歷爬坑,知道找到key的值所在的字典,如果沒有找到返回error涂臣,找到了之后進(jìn)行刪除并釋放內(nèi)存盾计,返回ok
int dictDeleteNoFree(dict *ht, const void *key)
  • 調(diào)用dictGenericDelete(ht,key,1)刪除值,但是并不釋放內(nèi)存赁遗。
int dictDelete(dict *ht, const void *key)
  • 調(diào)用dictGenericDelete(ht,key,0)刪除值署辉,并釋放內(nèi)存。
int _dictClear(dict *d, dictht *ht, void(callback)(void *))
  • 清除指定的ht整個(gè)字典內(nèi)容并岩四,釋放內(nèi)存哭尝。
void dictRelease(dict *d)
  • 調(diào)用_dictClear(d,&d->ht[0],NULL)調(diào)用_dictClear(d,&d->ht[1],NULL),分別釋放兩個(gè)hashtable
void *dictFetchValue(dict *d, const void *key)
  • 首先調(diào)用dictFind,然后獲得這個(gè)鍵對(duì)于的字典實(shí)體,進(jìn)而獲得字典值剖煌。
dictIterator *dictGetIterator(dict *d)
  • 創(chuàng)建dictIterator實(shí)體材鹦,并把內(nèi)容迭代的指針指向dict d。獲得的是不安全的迭代器耕姊,只能通過這個(gè)迭代器讀取字典桶唐,不能寫操作。
dictIterator *dictGetSafeIterator(dict *d)
  • 獲得安全的迭代器版本茉兰,可以對(duì)整個(gè)字典進(jìn)行讀寫操作尤泽。
dictEntry *dictNext(dictIterator *iter)
  • 獲得下一個(gè)字典元素指針。
  • 具體的操作:
  • 1.next不為空的時(shí)候直接返回next的內(nèi)容,這個(gè)時(shí)候迭代器里面保存了當(dāng)前hashtable的索引值坯约,所以可以熊咽,等到把這個(gè)鏈表遍歷完了,就可增加索引index的值闹丐,按著往數(shù)組后面開始遍歷另一個(gè)鏈表
  • 2.next為空的時(shí)候横殴,這個(gè)時(shí)候需要判斷,是否為安全的迭代器,如果是增加指紋信息(我猜測(cè)可能是為了后面進(jìn)行通過迭代器進(jìn)行修改字典進(jìn)行的驗(yàn)證)妇智,并且需要增加迭代器的引用個(gè)數(shù)滥玷。
  • 判斷一個(gè)表是否遍歷完了。如果這個(gè)表遍歷完了(通過index和桶的大形±狻)惑畴,并且正在進(jìn)行rehash那么就需要切換到另一個(gè)hashtable中進(jìn)行依次從數(shù)組的開始0進(jìn)行遍歷。
void dictReleaseIterator(dictIterator *iter)
  • 釋放迭代器
  • 如果為安全版本的迭代器航徙,需要減少字典迭代器上面的引用如贷,否則需要驗(yàn)證是否為當(dāng)前字典的指紋信息。
dictEntry *dictGetRandomKey(dict *d)
  • 隨機(jī)獲得字典中的一個(gè)鍵值對(duì)到踏。
  • 隨機(jī)找到一個(gè)索引杠袱,并且隨機(jī)獲得這個(gè)索引指向的鏈表中的一個(gè)元素。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窝稿,一起剝皮案震驚了整個(gè)濱河市楣富,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伴榔,老刑警劉巖纹蝴,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異踪少,居然都是意外死亡塘安,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門援奢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兼犯,“玉大人,你說我怎么就攤上這事集漾∏星” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵帆竹,是天一觀的道長绕娘。 經(jīng)常有香客問我,道長栽连,這世上最難降的妖魔是什么险领? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任侨舆,我火速辦了婚禮,結(jié)果婚禮上绢陌,老公的妹妹穿的比我還像新娘挨下。我一直安慰自己,他們只是感情好脐湾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布臭笆。 她就那樣靜靜地躺著,像睡著了一般秤掌。 火紅的嫁衣襯著肌膚如雪愁铺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天闻鉴,我揣著相機(jī)與錄音茵乱,去河邊找鬼。 笑死孟岛,一個(gè)胖子當(dāng)著我的面吹牛瓶竭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播渠羞,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼斤贰,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了次询?” 一聲冷哼從身側(cè)響起荧恍,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屯吊,沒想到半個(gè)月后块饺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雌芽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辨嗽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片世落。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖糟需,靈堂內(nèi)的尸體忽然破棺而出屉佳,到底是詐尸還是另有隱情,我是刑警寧澤洲押,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布武花,位于F島的核電站,受9級(jí)特大地震影響杈帐,放射性物質(zhì)發(fā)生泄漏体箕。R本人自食惡果不足惜专钉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望累铅。 院中可真熱鬧跃须,春花似錦、人聲如沸娃兽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽投储。三九已至第练,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間玛荞,已是汗流浹背娇掏。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冲泥,地道東北人驹碍。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像凡恍,于是被迫代替她去往敵國和親志秃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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