127.0.0.1:6379> set name jiang OK 127.0.0.1:6379> get name "jiang"
字典也是哈希键的底层实现。
1 2 3 4 5 6 7 8 9 10 11 12
127.0.0.1:6379> hset person name jiang (integer) 1 127.0.0.1:6379> hset person age 20 (integer) 1 127.0.0.1:6379> keys person 1) "person" 127.0.0.1:6379> hget person age "20" 127.0.0.1:6379> hdel person age (integer) 1 127.0.0.1:6379> type person hash
二、字典的实现
Redis的字典使用哈希表作为底层实现,一个哈希表有多个哈希表节点,一个节点对应一个键值对。
1、字典
字典的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/* * 字典 */ typedefstructdict {
// 类型特定函数 dictType *type;
// 私有数据 void *privdata;
// 哈希表 dictht ht[2];
// rehash 索引 // 当 rehash 不在进行时,值为 -1 int rehashidx; /* rehashing not in progress if rehashidx == -1 */
/* Note that rehashidx can't overflow as we are sure there are more * elements because ht[0].used != 0 */ // 确保 rehashidx 没有越界 assert(d->ht[0].size > (unsigned)d->rehashidx);
// 指向该索引的链表表头节点 de = d->ht[0].table[d->rehashidx]; /* Move all the keys in this bucket from the old to the new hash HT */ // 将链表中的所有节点迁移到新哈希表 // T = O(1) while(de) { unsignedint h;
// 保存下个节点的指针 nextde = de->next;
/* Get the index in the new hash table */ // 计算新哈希表的哈希值,以及节点插入的索引位置 h = dictHashKey(d, de->key) & d->ht[1].sizemask;
// 如果条件允许的话,进行单步 rehash // T = O(1) if (dictIsRehashing(d)) _dictRehashStep(d);
/* Get the index of the new element, or -1 if * the element already exists. */ // 计算键在哈希表中的索引值 // 如果值为 -1 ,那么表示键已经存在 // T = O(N) if ((index = _dictKeyIndex(d, key)) == -1) returnNULL;
// T = O(1) /* Allocate the memory and store the new entry */ // 如果字典正在 rehash ,那么将新键添加到 1 号哈希表 // 否则,将新键添加到 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++;
/* Set the hash entry fields. */ // 设置新节点的键 // T = O(1) dictSetKey(d, entry, key);
// 进行单步 rehash if (dictIsRehashing(d)) _dictRehashStep(d);
// 如果正在 rehash ,那么将 1 号哈希表也作为随机查找的目标 if (dictIsRehashing(d)) { // T = O(N) do { 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); }
/* Now we found a non empty bucket, but it is a linked * list and we need to get a random element from the list. * The only sane way to do so is counting the elements and * select a random index. */ // 目前 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;
staticint _dictExpandIfNeeded(dict *d) { /* Incremental rehashing already in progress. Return. */ // 渐进式 rehash 已经在进行了,直接返回 if (dictIsRehashing(d)) return DICT_OK;
/* If the hash table is empty expand it to the initial size. */ // 如果字典(的 0 号哈希表)为空,那么创建并返回初始化大小的 0 号哈希表 // T = O(1) 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. */ // 一下两个条件之一为真时,对字典进行扩展 // 1)字典已使用节点数和字典大小之间的比率接近 1:1 // 并且 dict_can_resize 为真 // 2)已使用节点数和字典大小之间的比率超过 dict_force_resize_ratio if (d->ht[0].used >= d->ht[0].size && (dict_can_resize || d->ht[0].used/d->ht[0].size > dict_force_resize_ratio)) { // 新哈希表的大小至少是目前已使用节点数的两倍 // T = O(N) return dictExpand(d, d->ht[0].used*2); }
/* the size is invalid if it is smaller than the number of * elements already inside the hash table */ // 不能在字典正在 rehash 时进行 // size 的值也不能小于 0 号哈希表的当前已使用节点 if (dictIsRehashing(d) || d->ht[0].used > size) return DICT_ERR;
/* Allocate the new hash table and initialize all pointers to NULL */ // 为哈希表分配空间,并将所有指针指向 NULL n.size = realsize; n.sizemask = realsize-1; // T = O(N) n.table = zcalloc(realsize*sizeof(dictEntry*)); n.used = 0;
/* Is this the first initialization? If so it's not really a rehashing * we just set the first hash table so that it can accept keys. */ // 如果 0 号哈希表为空,那么这是一次初始化: // 程序将新哈希表赋给 0 号哈希表的指针,然后字典就可以开始处理键值对了。 if (d->ht[0].table == NULL) { d->ht[0] = n; return DICT_OK; }