Dict和Java中的HashMap很相似,都是数组开链法解决冲突。

但是Redis为了高性能, 有很多比较微妙的方法,例如 数组的大小总是2的倍数,初始大小是4。

rehash并不是一次就执行完,而是分多次执行。每次执行一部分。其中rehashidx表示现在hash到哪一个桶啦,-1表示现在并没有rehash.

dict包含两个dicttable, 编号为0,1,  dictht0是直接存储哈希表的地方, dictht1在rehash中用到,当rehashidx不为-1时, 查找key,同时在dictht1和dictht0中查找。

数据结构

typedef struct dictEntry {void *key;union {void *val;uint64_t u64;int64_t s64;double d;} v;struct dictEntry *next;
} dictEntry;typedef struct dictType {unsigned int (*hashFunction)(const void *key);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);void (*valDestructor)(void *privdata, void *obj);
} dictType;/* This is our hash table structure. Every dictionary has two of this as we* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {dictEntry **table;unsigned long size;unsigned long sizemask;unsigned long used;
} dictht;typedef struct dict {dictType *type;void *privdata;dictht ht[2];long rehashidx; /* rehashing not in progress if rehashidx == -1 */int iterators; /* number of iterators currently running */
} dict;/* If safe is set to 1 this is a safe iterator, that means, you can call* dictAdd, dictFind, and other functions against the dictionary even while* iterating. Otherwise it is a non safe iterator, and only dictNext()* should be called while iterating. */
typedef struct dictIterator {dict *d;long index;int table, safe;dictEntry *entry, *nextEntry;/* unsafe iterator fingerprint for misuse detection. */long long fingerprint;
} dictIterator;typedef void (dictScanFunction)(void *privdata, const dictEntry *de);

查找key

dictEntry *dictFind(dict *d, const void *key)
{dictEntry *he;unsigned int h, idx, table;if (d->ht[0].size == 0) return NULL; /* We don't have a table at all */if (dictIsRehashing(d)) _dictRehashStep(d);h = dictHashKey(d, key);for (table = 0; table <= 1; table++) {idx = h & d->ht[table].sizemask;he = d->ht[table].table[idx];while(he) {if (dictCompareKeys(d, key, he->key))return he;he = he->next;}if (!dictIsRehashing(d)) return NULL;}return NULL;
}

redis的rehash是增量rehash,每次rehash一部分

rehash过程:

1. 从 dictht0的table 0到----N-1查找不为NULL的位置(非空桶)

2. 对该位置的链表进行处理, hash到dictht 1的table 1中。

rehash的函数,设置了n参数,表示要处理的非空桶的个数,但是在函数内部设置了最多访问10*n个空桶。

int dictRehash(dict *d, int n) {int empty_visits = n*10; /* Max number of empty buckets to visit. */if (!dictIsRehashing(d)) return 0;while(n-- && d->ht[0].used != 0) {dictEntry *de, *nextde;/* Note that rehashidx can't overflow as we are sure there are more* elements because ht[0].used != 0 */assert(d->ht[0].size > (unsigned long)d->rehashidx);while(d->ht[0].table[d->rehashidx] == NULL) {d->rehashidx++;if (--empty_visits == 0) return 1;}de = d->ht[0].table[d->rehashidx];/* Move all the keys in this bucket from the old to the new hash HT */while(de) {unsigned int h;nextde = de->next;/* Get the index in the new hash table */h = dictHashKey(d, de->key) & d->ht[1].sizemask;de->next = d->ht[1].table[h];d->ht[1].table[h] = de;d->ht[0].used--;d->ht[1].used++;de = nextde;}d->ht[0].table[d->rehashidx] = NULL;d->rehashidx++;}/* Check if we already rehashed the whole table... */if (d->ht[0].used == 0) {zfree(d->ht[0].table);d->ht[0] = d->ht[1];_dictReset(&d->ht[1]);d->rehashidx = -1;return 0;}/* More to rehash... */return 1;
}

和adlist一样,dict也有迭代器

迭代方法如下:

dictEntry *dictNext(dictIterator *iter)
{//对表的桶进行遍历,直到找到一个非空桶,返回while (1) {if (iter->entry == NULL) {dictht *ht = &iter->d->ht[iter->table];if (iter->index == -1 && iter->table == 0) {if (iter->safe)iter->d->iterators++;elseiter->fingerprint = dictFingerprint(iter->d);//对dict进行指纹
            }iter->index++;//如果迭代到表的最后一个桶,就判断要不要迭代第二个表if (iter->index >= (long) ht->size) {if (dictIsRehashing(iter->d) && iter->table == 0) {iter->table++;iter->index = 0;ht = &iter->d->ht[1];} else {break;}}iter->entry = ht->table[iter->index];} else {iter->entry = iter->nextEntry;}if (iter->entry) {/* We need to save the 'next' here, the iterator user* may delete the entry we are returning. */iter->nextEntry = iter->entry->next;return iter->entry;}}return NULL;
}

Dict的API如下:

/* API */
/* 字典创建, type参数制定各类对字典的自定义函数,会初始化dictht, dict */
dict *dictCreate(dictType *type, void *privDataPtr);
int dictExpand(dict *d, unsigned long size);
/* 添加键值对,内部调用addRaw和setvalue ,如果已经存在,返回NULL*/
int dictAdd(dict *d, void *key, void *val);
/* 添加键 ,如果已经存在,返回NULL*/
dictEntry *dictAddRaw(dict *d, void *key);
/* 添加一个key,如果存在,直接设置value,设置key的value */
int dictReplace(dict *d, void *key, void *val);
/* 添加一个key,如果存在,直接返回 */
dictEntry *dictReplaceRaw(dict *d, void *key);
/* 删除一个节点,需要free那个节点 */
int dictDelete(dict *d, const void *key);
/* 删除一个节点,不需要free那个节点 */
int dictDeleteNoFree(dict *d, const void *key);
/* 删除dict*/
void dictRelease(dict *d);
/* 查找key*/
dictEntry * dictFind(dict *d, const void *key);
/* 查找key的value*/
void *dictFetchValue(dict *d, const void *key);
/* 将dict的size设置和元素数量一样,但是符合2的倍数*/
int dictResize(dict *d);
dictIterator *dictGetIterator(dict *d);
dictIterator *dictGetSafeIterator(dict *d);
dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *d);
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
void dictPrintStats(dict *d);
unsigned int dictGenHashFunction(const void *key, int len);
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
void dictEmpty(dict *d, void(callback)(void*));
void dictEnableResize(void);
void dictDisableResize(void);
int dictRehash(dict *d, int n);
/* rehash,设置一个最长时间*/
int dictRehashMilliseconds(dict *d, int ms);
void dictSetHashFunctionSeed(unsigned int initval);
unsigned int dictGetHashFunctionSeed(void);
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, void *privdata);

上面的API很多函数内部都会判断当前是不是还在rehash状态,如果是,就rehash一步。

在rehash前,会判断是不是有迭代器存在,如果有迭代器存在,就不rehash

static void _dictRehashStep(dict *d) {
if (d->iterators == 0) dictRehash(d,1);
}

转载于:https://www.cnblogs.com/stonehat/p/4855781.html

Redis源码阅读-Dict哈希字典相关推荐

  1. redis源码阅读-持久化之RDB

    持久化介绍: redis的持久化有两种方式: rdb :可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot) aof : 记录redis执行的所有写操作命令 根 ...

  2. redis源码阅读-zset

    前段时间给小伙伴分享redis,顺带又把redis撸了一遍了,对其源码,又有了比较深入的了解.(ps: 分享的文章再丰富下再放出来). 数据结构 我们先看下redis 5.0的代码.本次讲解主要是zs ...

  3. Redis源码阅读-Adlist双向链表

    Redis源码阅读-链表部分- 链表数据结构在Adlist.h   Adlist.c Redis的链表是双向链表,内部定义了一个迭代器. 双向链表的函数主要是链表创建.删除.节点插入.头插入.尾插入. ...

  4. redis源码阅读-持久化之aof与aof重写详解

    aof相关配置 aof-rewrite-incremental-fsync yes # aof 开关,默认是关闭的,改为yes表示开启 appendonly no # aof的文件名,默认 appen ...

  5. redis源码阅读--hashTable

    为什么80%的码农都做不了架构师?>>>    公司有个项目用到hash,同事从redis的代码里抠了hashTable的源码出来,最近抽空看了一下,设计还是很精妙的. 现在把阅读的 ...

  6. redis源码阅读(1)

    redis 是c 编写的,首先看下redis 代码目录结构(对应版本3.25): 开发相关的放在deps下面: 主要代码放置在deps和src下面,utils 下面放置的是rb 脚本 首先看下src ...

  7. Redis源码阅读,从入门到放弃

    作为后端工程师,我们在面试和工作中都会用到 Redis,特别是大型互联网公司面试时,不仅要求面试者能简单使用 Redis,还要求懂 Redis 源码层面的实现原理,具备解决常见问题的能力.可以说,熟练 ...

  8. redis 源码阅读

    双向链表(adlist.h/adlist.c) - redis 源码解析 1.0 documentation (redissrc.readthedocs.io) 如何阅读 Redis 源码? - bl ...

  9. Redis源码阅读01-读了一下redis启动流程涉及的源码我都读了个啥

    阅读源码是学习一门技术的必经之路,经过1周左右的c语言入门学习,我就开始硬读redis的源码了.因为公司的多版本的改造,所以源码就选择redis6.x的最高版本redis6.2.7. 在阅读源码前,首 ...

  10. Redis源码阅读笔记(1)——简单动态字符串sds实现原理

    首先,sds即simple dynamic string,redis实现这个的时候使用了一个技巧,并且C99将其收录为标准,即柔性数组成员(flexible array member),参考资料见这里 ...

最新文章

  1. js文件引用方式及其同步执行与异步执行
  2. gradle 构建包含源码配置
  3. docker 覆盖 entrypoint_最佳实践,Dockerfile中ENTRYPOINT与CMD指令的区别与建议
  4. 第六十七期:Python爬虫44万条数据揭秘:如何成为网易音乐评论区的网红段子手
  5. mysql8.0.13 rpm_Centos7 安装mysql 8.0.13(rpm)的教程详解
  6. 36日期计算包含计算某月某日是星期几的公式
  7. 学习TeXworks编辑器(一)自定义快捷键详解
  8. 无线(互联网)+有线(内网)上外网设置
  9. 关于unity如何制作mmo
  10. edge浏览器如何新建IE tab
  11. 结对开发项目--石家庄地铁web版
  12. 让ImageMagick支持png和jpeg格式
  13. 第二天性-人类进化的经济起源
  14. Anroid中Service详解
  15. 对挣钱与财富等三个问题的思考
  16. 贪心--CF645E
  17. 切换到/etc/ppp/ipup文件,写出操作命令过程,并显示详细过程,和推迟30秒关机命令
  18. 打造更完美的小程序商城
  19. 运行剑灵与服务器断开,《剑灵》与服务器断开链接1000\3000的解决办法
  20. 合肥python培训价格

热门文章

  1. 终于把tomcat给搞定了
  2. Activity初级:startActivityForResult、重写onActivityResult、setResult回传数据、requestCode请求码...
  3. Expression Blend实例中文教程(5) - 布局控件快速入门StackPanel,ScrollViewer和Border
  4. 活动目录概念和灾难恢复
  5. arm linux内核启动过程详解
  6. java格式化word文档_Java如何格式化word文档中的文本?
  7. Linux内核入门(五)——必要的硬件知识
  8. android gms包找不到,错误:包com.google.android.gms.appstate不存在
  9. next数组_数据结构之数组与链表
  10. Web安全之SQL注入攻击技巧与防范