Redis(hash)
基础结构
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;
其中type包含一系列哈希表需要用的函数,dictht类型的数组ht[2]表示两个哈希表实例,由rehashidx指明下一个需要扩容的哈希实例的编号,iterators记录外部使用哈希表的迭代器的数目。
其中type类型定义如下:
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;
其中包括对key值的复制,value的复制,key值比较,构造,析构,hash函数。
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
dictht为哈希表具体实现的结构体,table指向哈希中的记录,用数组+开链的形式保存记录;size表示哈希表桶的大小,为2的指数;sizemark=size-1,方便哈希值根据size取模;used记录了哈希表中的记录数目。
自动扩容
Redis的hash表的容量不够时可以0/1切换,自动扩容。
void dictEnableResize(void) {
dict_can_resize = 1;
}
void dictDisableResize(void) {
dict_can_resize = 0;
}
通过以上2个函数表示当前hash表是否可以自动扩容。可以通过这两个函数设置当前hash表是否可以自动扩容。
/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
/* Incremental rehashing already in progress. Return. */
if (dictIsRehashing(d)) return DICT_OK;
/* If the hash table is empty expand it to the initial size. */
if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
/* If we reached the 1:1 ratio, and we are allowed to resize the hash
* table (global setting) or we should avoid it but the ratio between
* elements/buckets is over the "safe" threshold, we resize doubling
* the number of buckets. */
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;
}
应用程序可以使用dictResize()扩容,它首先判断是否允许扩容,及是否正在扩容。若可以扩容,则调用dictExpand()扩容。然后应用程序需要调用dictRehashMilliseconds()启动扩容过程,并指定扩容过程中记录拷贝速度。除了应用程序指定的扩容外,在调用dictAdd()往哈希中添加记录时,系统也会通过调用_dictExpandIfNeeded()判断是否需要扩容。_dictExpandIfNeeded()中,如果正在扩容,则不会重复进行扩容;如果哈希表size=0,即桶数目为0,则扩容到初始大小;否则如果used>=size,并且can_resize==1或used/size超过阀值(默认为5)时,以max(used, size)的两倍为基数,调用dictExpand()扩容。
迭代器
dictGetIterator()获取迭代器:
dictIterator *dictGetIterator(dict *d)
{
dictIterator *iter = zmalloc(sizeof(*iter));
iter->d = d;
iter->table = 0;
iter->index = -1;
iter->safe = 0;
iter->entry = NULL;
iter->nextEntry = NULL;
return iter;
}
迭代器迭代器提供了遍历哈希表中所有元素的方法。
通过dictGetIterator()获得迭代器后,使用dictNext(dictIterator *)方法获得下一个元素。
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++;
else
iter->fingerprint = dictFingerprint(iter->d);
}
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;
}
当外部持有的迭代器数目不为0时,哈希表会暂停扩容操作。这时开始遍历,保证遍历的数目是最新的。迭代器遍历的过程,从ht[0]开始,依次从第一个桶table[0]开始遍历桶中的元素,然后时table[1], table[2], ..., table[size],若正在扩容,则会继续遍历ht[1]中的桶。遍历桶中元素时,依次访问链表中的每个元素。
参考《Redis源代码分析》