redis源码6--字典Dict:查找、删除、随机获取key等

主要函数

//返回字典中包含键 key 的节点,找到返回节点,找不到返回 NULL
dictEntry *dictFind(dict *d, const void *key)
// 如果给定key不存在,将key插入到字典,如果key存在,返回该key的字典节点
dictEntry *dictReplaceRaw(dict *d, void *key) 
//查找并删除包含给定键的节点,参数 nofree 决定是否调用键和值的释放函数。0 表示调用,1 表示不调用
static int dictGenericDelete(dict *d, const void *key, int nofree)
//从字典中删除包含给定键的节点,并且调用键值的释放函数来删除键值
int dictDelete(dict *ht, const void *key) 
//从字典中删除包含给定键的节点,但不调用键值的释放函数来删除键值
int dictDeleteNoFree(dict *ht, const void *key)
//删除哈希表上的所有节点,并重置哈希表的各项属性
int _dictClear(dict *d, dictht *ht, void(callback)(void *))
//删除并释放整个字典
void dictRelease(dict *d)
//清空字典上的所有哈希表节点,并重置字典属性
void dictEmpty(dict *d, void(callback)(void*))
//获取包含给定键的节点的值
void *dictFetchValue(dict *d, const void *key)
//随机返回字典中任意一个节点。可用于实现随机化算法。
dictEntry *dictGetRandomKey(dict *d)
//返回连续的count个节点,开头的节点随机,要保证des有充足的空间
int dictGetRandomKeys(dict *d, dictEntry **des, int count)

dictFind

返回字典中包含键 key 的节点,找到返回节点,找不到返回 NULL

dictEntry *dictFind(dict *d, const void *key)
{
  dictEntry *he;
  unsigned int h, idx, table;

  // 字典(的哈希表)为空
  if (d->ht[0].size == 0) return NULL; 

  // 如果条件允许的话,进行单步 rehash
  if (dictIsRehashing(d)) _dictRehashStep(d);

  // 计算键的哈希值
  h = dictHashKey(d, key);
  // 在字典的哈希表中查找这个键
  // T = O(1)
  for (table = 0; table <= 1; table++) {

    // 计算索引值
    idx = h & d->ht[table].sizemask;

    // 遍历给定索引上的链表的所有节点,查找 key
    he = d->ht[table].table[idx];
    // T = O(1)
    while(he) {

        if (dictCompareKeys(d, key, he->key))
            return he;

        he = he->next;
    }

    // 如果程序遍历完 0 号哈希表,仍然没找到指定的键的节点
    // 那么程序会检查字典是否在进行 rehash ,
    // 如果正在rehash,继续查找1号哈希表,否则返回NULL
    if (!dictIsRehashing(d)) return NULL;
  }

  // 进行到这里时,说明两个哈希表都没找到
  return NULL;
}

dictReplace

将给定的键值对添加到字典中,如果键已经存在,那么删除旧有的键值对。
如果键值对为全新添加,那么返回 1 。
如果键值对是通过对原有的键值对更新得来的,那么返回 0 。
T = O(N)

int dictReplace(dict *d, void *key, void *val)
{
  dictEntry *entry, auxentry;

  // 尝试直接将键值对添加到字典
  // 如果键 key 不存在的话,添加会成功
  // T = O(N)
  if (dictAdd(d, key, val) == DICT_OK)
    return 1;

  // 运行到这里,说明键 key 已经存在,那么找出包含这个 key 的节点
  // T = O(1)
  entry = dictFind(d, key);

  // 先保存原有的值的指针
  auxentry = *entry;
  // 然后设置新的值
  // T = O(1)
  dictSetVal(d, entry, val);
  // 然后释放旧值
  // T = O(1)
  dictFreeVal(d, &auxentry);

  return 0;
}

dictReplaceRaw

如果给定key不存在,将key插入到字典,如果key存在,返回该key的字典节点

dictEntry *dictReplaceRaw(dict *d, void *key) {

  // 使用 key 在字典中查找节点
  // T = O(1)
  dictEntry *entry = dictFind(d,key);

  // 如果节点找到了直接返回节点,否则添加并返回一个新节点
  // T = O(N)
  return entry ? entry : dictAddRaw(d,key);
}

dictGenericDelete

静态函数,查找并删除包含给定键的节点
参数 nofree 决定是否调用键和值的释放函数,0 表示调用,1 表示不调用
找到并成功删除返回 DICT_OK ,没找到则返回 DICT_ERR
T = O(1)

static int dictGenericDelete(dict *d, const void *key, int nofree)
{
  unsigned int h, idx;
  dictEntry *he, *prevHe;
  int table;

  // 字典(的哈希表)为空
  if (d->ht[0].size == 0) return DICT_ERR; /* d->ht[0].table is NULL */

  // 进行单步 rehash ,T = O(1)
  if (dictIsRehashing(d)) _dictRehashStep(d);

  // 计算哈希值
  h = dictHashKey(d, key);

  // 遍历哈希表
  // T = O(1)
  for (table = 0; table <= 1; table++) {

    // 计算索引值 
    idx = h & d->ht[table].sizemask;
    // 指向该索引上的链表
    he = d->ht[table].table[idx];
    prevHe = NULL;
    // 遍历链表上的所有节点
    // T = O(1)
    while(he) {
    
        if (dictCompareKeys(d, key, he->key)) {
            // 超找目标节点

            /* Unlink the element from the list */
            // 从链表中删除
            if (prevHe)
                prevHe->next = he->next;
            else
                d->ht[table].table[idx] = he->next;

            // 释放调用键和值的释放函数?
            if (!nofree) {
                dictFreeKey(d, he);
                dictFreeVal(d, he);
            }
            
            // 释放节点本身
            zfree(he);

            // 更新已使用节点数量
            d->ht[table].used--;

            // 返回已找到信号
            return DICT_OK;
        }

        prevHe = he;
        he = he->next;
    }

    // 如果执行到这里,说明在 0 号哈希表中找不到给定键
    // 那么根据字典是否正在进行 rehash ,决定要不要查找 1 号哈希表
    if (!dictIsRehashing(d)) break;
  }

  // 没找到
  return DICT_ERR; /* not found */
}

dictDelete

从字典中删除包含给定键的节点,并且调用键值的释放函数来删除键值

int dictDelete(dict *ht, const void *key) {
  return dictGenericDelete(ht,key,0);
}

dictDeleteNoFree

从字典中删除包含给定键的节点,但不调用键值的释放函数来删除键值

int dictDeleteNoFree(dict *ht, const void *key) {
  return dictGenericDelete(ht,key,1);
}

_dictClear

删除哈希表上的所有节点,并重置哈希表的各项属性
T = O(N)

int _dictClear(dict *d, dictht *ht, void(callback)(void *)) {
  unsigned long i;

  // 遍历整个哈希表
  // T = O(N)
  for (i = 0; i < ht->size && ht->used > 0; i++) {
    dictEntry *he, *nextHe;

    if (callback && (i & 65535) == 0) callback(d->privdata);

    // 跳过空索引
    if ((he = ht->table[i]) == NULL) continue;

    // 遍历整个链表
    // T = O(1)
    while(he) {
        nextHe = he->next;
        // 删除键
        dictFreeKey(d, he);
        // 删除值
        dictFreeVal(d, he);
        // 释放节点
        zfree(he);

        // 更新已使用节点计数
        ht->used--;

        // 处理下个节点
        he = nextHe;
    }
  }

  // 释放哈希表结构
  zfree(ht->table);

  // 重置哈希表属性
  _dictReset(ht);

  return DICT_OK; /* never fails */
}

dictRelease

删除并释放整个字典
T = O(N)

void dictRelease(dict *d)
{
  // 删除并清空两个哈希表
  _dictClear(d,&d->ht[0],NULL);
  _dictClear(d,&d->ht[1],NULL);
  // 释放节点结构
  zfree(d);
}

dictEmpty

清空字典上的所有哈希表节点,并重置字典属性

void dictEmpty(dict *d, void(callback)(void*)) {

  // 删除两个哈希表上的所有节点
  // T = O(N)
  _dictClear(d,&d->ht[0],callback);
  _dictClear(d,&d->ht[1],callback);
  // 重置属性 
  d->rehashidx = -1;
  d->iterators = 0;
}

dictFetchValue

获取包含给定键的节点的值
如果节点不为空,返回节点的值,否则返回 NULL
T = O(1)

void *dictFetchValue(dict *d, const void *key) {
  dictEntry *he;

  // T = O(1)
  he = dictFind(d,key);

  return he ? dictGetVal(he) : NULL;
}

dictGetRandomKey

随机返回字典中任意一个节点。可用于实现随机化算法。如果字典为空,返回 NULL 。
T = O(N)

dictEntry *dictGetRandomKey(dict *d)
{
  dictEntry *he, *orighe;
  unsigned int h;
  int listlen, listele;

  // 字典为空
  if (dictSize(d) == 0) return NULL;

  // 进行单步 rehash
  if (dictIsRehashing(d)) _dictRehashStep(d);

  // 如果正在 rehash ,那么将 1 号哈希表也作为随机查找的目标
  if (dictIsRehashing(d)) {
    // T = O(N)
    do {
        //随机取一个索引,范围在两个哈希表的size相加的区间内,相当于将两个哈希表的table拼接起来,ht[0]作为前半段,ht[1]作为后半段
        h = random() % (d->ht[0].size+d->ht[1].size);
        he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :
                                  d->ht[0].table[h];
        //该索引下可能没有节点,所以要继续循环找,直到找到有节点的索引
    } while(he == NULL);
  // 否则,只从 0 号哈希表中查找节点
  } else {
    // T = O(N)
    do {
        h = random() & d->ht[0].sizemask;
        he = d->ht[0].table[h];
    } while(he == NULL);
  }

 // 目前 he 已经指向一个非空的节点链表
  // 程序将从这个链表随机返回一个节点
  listlen = 0;
  orighe = he;
  // 计算节点数量, T = O(1)
  while(he) {
    he = he->next;
    listlen++;
  }
  // 取模,得出随机节点的索引
  listele = random() % listlen;
  he = orighe;
  // 按索引查找节点
  // T = O(1)
  while(listele--) he = he->next;

  // 返回随机节点
  return he;
}

dictGetRandomKeys

返回count个连续的节点,第一个节点随机取,然后直接取第一个节点后面的count -1 个,函数调用时,要保证des有充足的空间

int dictGetRandomKeys(dict *d, dictEntry **des, int count) {
  //哈希表下标
  int j; 
   //用于判断取到的节点数量是否足够了
  int stored = 0;
  //当字典没有那么多节点时,调整count
  if (dictSize(d) < count) count = dictSize(d);

  while(stored < count) {
    for (j = 0; j < 2; j++) {
       //随机取一个索引
        unsigned int i = random() & d->ht[j].sizemask;
        int size = d->ht[j].size;

        //遍历该哈希表的所有索引
        while(size--) {
            //指向该索引链表的第一个节点
            dictEntry *he = d->ht[j].table[i];
            while (he) {
                //该索引链表中有节点,取出来
                *des = he;
                des++;
                //指向该链表的下一个节点
                he = he->next;
                stored++;
                //取出的节点足够了,函数返回
                if (stored == count) return stored;
            }
            //如果该索引下没有节点,或者索引链表下节点都取出来了,遍历下一个索引
            i = (i+1) & d->ht[j].sizemask;
        }
        //如果想访问ht[1]哈希表,需要字典正在进行 rehash,不然不能访问ht[1]
        assert(dictIsRehashing(d) != 0);
    }
  }
  return stored; /* Never reached. */
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343