您好,欢迎访问一九零五行业门户网

redis的hash怎么实现的

0.前言redis是kv型的内存数据库, 数据库存储的核心就是hash表, 我们执行select命令选择一个存储的db之后, 所有的操作都是以hash表为基础的, 下面会分析下redis的hash数据结构和实现.
1.hash数据结构/*hash表一个节点包含key,value数据对 */typedef struct dictentry { void *key; union { void *val; uint64_t u64; int64_t s64; double d; } v; struct dictentry *next; /* 指向下一个节点, 链接表的方式解决hash冲突 */} 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;typedef struct dictht { dictentry **table; /* dictentry*数组,hash表 */ unsigned long size; /* hash表总大小 */ unsigned long sizemask; /* 计算在table中索引的掩码, 值是size-1 */ unsigned long used; /* hash表已使用的大小 */} dictht;typedef struct dict { dicttype *type; void *privdata; dictht ht[2]; /* 两个hash表,rehash时使用*/ long rehashidx; /* rehash的索引, -1表示没有进行rehash */ int iterators; /* */} dict;
2.hash数据结构图
3.渐进式hash说明dict中ht[2]中有两个hash表, 我们第一次存储数据的数据时, ht[0]会创建一个最小为4的hash表, 一旦ht[0]中的size和used相等, 则dict中会在ht[1]创建一个size*2大小的hash表, 此时并不会直接将ht[0]中的数据copy进ht[0]中, 执行的是渐进式rehash, 即在以后的操作(find, set, get等)中慢慢的copy进去, 以后新添加的元素会添加进ht[0], 因此在ht[1]被占满的时候定能确保ht[0]中所有的数据全部copy到ht[1]中.
4.创建hash表创建hash表过程非常简单,直接调用dictcreate函数, 分配一块内存,初始化中间变量即可.
dict *dictcreate(dicttype *type, void *privdataptr){ /*分配内存*/ dict *d = zmalloc(sizeof(*d)); /*初始化操作*/ _dictinit(d,type,privdataptr); return d;}
5.添加元素hash表中添加元素,首先判断空间是否足够, 然后计算key对应的hash值, 然后将需要添加的key和value放入表中.
int dictadd(dict *d, void *key, void *val){ /*添加入hash表中, 返回新添加元素的实体结构体*/ dictentry *entry = dictaddraw(d,key); if (!entry) return dict_err; /*元素val值放入元素实体结构中*/ dictsetval(d, entry, val); return dict_ok;}/**添加元素实体函数*/dictentry *dictaddraw(dict *d, void *key){ int index; dictentry *entry; dictht *ht; if (dictisrehashing(d)) _dictrehashstep(d); /*根据key值计算新元素在hash表中的索引, 返回-1则表示元素已存在, 直接返回null*/ if ((index = _dictkeyindex(d, key)) == -1) return null; /*如果在进行rehash过程,则新元素添加到ht[1]中, 否则添加到ht[0]中 */ ht = dictisrehashing(d) ? &d->ht[1] : &d->ht[0]; entry = zmalloc(sizeof(*entry)); entry->next = ht->table[index]; ht->table[index] = entry; ht->used++; /*设置元素key*/ dictsetkey(d, entry, key); return entry;}/**计算索引的函数*/static int _dictkeyindex(dict *d, const void *key){ unsigned int h, idx, table; dictentry *he; /* 判断hash表是否空间足够, 不足则需要扩展 */ if (_dictexpandifneeded(d) == dict_err) return -1; /* 计算key对应的hash值 */ h = dicthashkey(d, key); for (table = 0; table <= 1; table++) { /*计算索引*/ idx = h & d->ht[table].sizemask; /*遍历冲突列表, 判断需要查找的key是否已经在冲突列表中*/ he = d->ht[table].table[idx]; while(he) { if (dictcomparekeys(d, key, he->key)) return -1; he = he->next; } if (!dictisrehashing(d)) break; } return idx;}/**判断hash表是否需要扩展空间*/static int _dictexpandifneeded(dict *d){ /*redis的rehash采用的渐进式hash, rehash时分配了原来两倍的内存空间, 在rehash阶段空间必定够用*/ if (dictisrehashing(d)) return dict_ok; /* hash表是空的需要初始化空间, 默认是4*/ if (d->ht[0].size == 0) return dictexpand(d, dict_ht_initial_size); /* 已使用空间满足不了设置的条件*/ if (d->ht[0].used >= d->ht[0].size && (dict_can_resize || d->ht[0].used/d->ht[0].size > dict_force_resize_ratio)) { /*扩展空间, 使用空间的两倍*/ return dictexpand(d, d->ht[0].used*2); } return dict_ok;}/**扩展空间或者初始化hash表空间*/int dictexpand(dict *d, unsigned long size){ dictht n; /* 对需要分配大小圆整为2的倍数 */ unsigned long realsize = _dictnextpower(size); /* 如果空间足够则表明调用错误 */ if (dictisrehashing(d) || d->ht[0].used > size) return dict_err; n.size = realsize; n.sizemask = realsize-1; n.table = zcalloc(realsize*sizeof(dictentry*)); n.used = 0; /*hash表为空初始化hash表*/ if (d->ht[0].table == null) { d->ht[0] = n; return dict_ok; } /*新分配的空间放入ht[1], 后面一步一步进行rehash*/ d->ht[1] = n; d->rehashidx = 0; return dict_ok;}
6.查找元素查找元素过程,首先计算hash值, 然后计算在ht[0]和ht[1]中索引位置, 进行查找.
dictentry *dictfind(dict *d, const void *key){ dictentry *he; unsigned int h, idx, table; if (d->ht[0].size == 0) return null; /*如果正在进行rehash, 执行一次rehash*/ if (dictisrehashing(d)) _dictrehashstep(d); h = dicthashkey(d, key); /*由于可能正在rehash, 因此要从ht[0]和ht[1]中分别进行查找, 找不到返回null*/ 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;}
7.删除元素删除元素首先查找元素, 然后将元素从hash表中移除即可, 调用dictdelete删除元素, 会同时删除元素所占空间
int dictdelete(dict *ht, const void *key) { return dictgenericdelete(ht,key,0);}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; 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]; prevhe = null; /*查找元素到元素,进行删除操作, 并释放占用的内存*/ 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; } if (!dictisrehashing(d)) break; } return dict_err; /* not found */}
hash命令hash命令操作都比较简单,需要注意的是当我们创建hash表示默认存储结构,并不是dict,而是ziplist结构,可以参考redis之ziplist数据结构,hash_max_ziplist_entries和hash_max_ziplist_value值作为阀值,hash_max_ziplist_entries表示一旦ziplist中元素数量超过该值,则需要转换为dict结构;hash_max_ziplist_value表示一旦ziplist中数据长度大于该值,则需要转换为dict结构。
更多redis相关技术文章,请访问redis教程栏目进行学习!
以上就是redis的hash怎么实现的的详细内容。
其它类似信息

推荐信息