字典

dictht 是一个散列表结构,使用拉链法解决哈希冲突。

  1. /* This is our hash table structure. Every dictionary has two of this as we
  2. * implement incremental rehashing, for the old to the new table. */
  3. typedef struct dictht {
  4. dictEntry **table;
  5. unsigned long size;
  6. unsigned long sizemask;
  7. unsigned long used;
  8. } dictht;
  1. typedef struct dictEntry {
  2. void *key;
  3. union {
  4. void *val;
  5. uint64_t u64;
  6. int64_t s64;
  7. double d;
  8. } v;
  9. struct dictEntry *next;
  10. } dictEntry;

Redis 的字典 dict 中包含两个哈希表 dictht,这是为了方便进行 rehash 操作。在扩容时,将其中一个 dictht 上的键值对 rehash 到另一个 dictht 上面,完成之后释放空间并交换两个 dictht 的角色。

  1. typedef struct dict {
  2. dictType *type;
  3. void *privdata;
  4. dictht ht[2];
  5. long rehashidx; /* rehashing not in progress if rehashidx == -1 */
  6. unsigned long iterators; /* number of iterators currently running */
  7. } dict;

rehash 操作不是一次性完成,而是采用渐进方式,这是为了避免一次性执行过多的 rehash 操作给服务器带来过大的负担。

渐进式 rehash 通过记录 dict 的 rehashidx 完成,它从 0 开始,然后每执行一次 rehash 都会递增。例如在一次 rehash 中,要把 dict[0] rehash 到 dict[1],这一次会把 dict[0] 上 table[rehashidx] 的键值对 rehash 到 dict[1] 上,dict[0] 的 table[rehashidx] 指向 null,并令 rehashidx++。

在 rehash 期间,每次对字典执行添加、删除、查找或者更新操作时,都会执行一次渐进式 rehash。

采用渐进式 rehash 会导致字典中的数据分散在两个 dictht 上,因此对字典的查找操作也需要到对应的 dictht 去执行。

  1. /* Performs N steps of incremental rehashing. Returns 1 if there are still
  2. * keys to move from the old to the new hash table, otherwise 0 is returned.
  3. *
  4. * Note that a rehashing step consists in moving a bucket (that may have more
  5. * than one key as we use chaining) from the old to the new hash table, however
  6. * since part of the hash table may be composed of empty spaces, it is not
  7. * guaranteed that this function will rehash even a single bucket, since it
  8. * will visit at max N*10 empty buckets in total, otherwise the amount of
  9. * work it does would be unbound and the function may block for a long time. */
  10. int dictRehash(dict *d, int n) {
  11. int empty_visits = n * 10; /* Max number of empty buckets to visit. */
  12. if (!dictIsRehashing(d)) return 0;
  13. while (n-- && d->ht[0].used != 0) {
  14. dictEntry *de, *nextde;
  15. /* Note that rehashidx can't overflow as we are sure there are more
  16. * elements because ht[0].used != 0 */
  17. assert(d->ht[0].size > (unsigned long) d->rehashidx);
  18. while (d->ht[0].table[d->rehashidx] == NULL) {
  19. d->rehashidx++;
  20. if (--empty_visits == 0) return 1;
  21. }
  22. de = d->ht[0].table[d->rehashidx];
  23. /* Move all the keys in this bucket from the old to the new hash HT */
  24. while (de) {
  25. uint64_t h;
  26. nextde = de->next;
  27. /* Get the index in the new hash table */
  28. h = dictHashKey(d, de->key) & d->ht[1].sizemask;
  29. de->next = d->ht[1].table[h];
  30. d->ht[1].table[h] = de;
  31. d->ht[0].used--;
  32. d->ht[1].used++;
  33. de = nextde;
  34. }
  35. d->ht[0].table[d->rehashidx] = NULL;
  36. d->rehashidx++;
  37. }
  38. /* Check if we already rehashed the whole table... */
  39. if (d->ht[0].used == 0) {
  40. zfree(d->ht[0].table);
  41. d->ht[0] = d->ht[1];
  42. _dictReset(&d->ht[1]);
  43. d->rehashidx = -1;
  44. return 0;
  45. }
  46. /* More to rehash... */
  47. return 1;
  48. }

跳跃表

是有序集合的底层实现之一。

跳跃表是基于多指针有序链表实现的,可以看成多个有序链表。

三、数据结构 - 图1

在查找时,从上层指针开始查找,找到对应的区间之后再到下一层去查找。下图演示了查找 22 的过程。

三、数据结构 - 图2

与红黑树等平衡树相比,跳跃表具有以下优点:

  • 插入速度非常快速,因为不需要进行旋转等操作来维护平衡性;
  • 更容易实现;
  • 支持无锁操作。