redis的字典底层实现是hash表,用来存储K-V对,其中K是唯一的。
了解java中hashmap的话,那么字典就很好理解了,完全可以参照hashmap的结构。
下面是hash表的结构:
typedef struct dictht {
dictEntry **table; /* hash表数组 */
unsigned long size; /* hash表大小 */
unsigned long sizemask; /* 用来计算索引,sizemask=size-1 */
unsigned long used; /* hash表中已经被使用的节点的数量 */
} dictht;
其中**table是指向dictEntry的指针,dictEntry结构如下:
typedef struct dictEntry {
void *key; /* 键 */
union {
void *val; /* 值 */
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; /* 指向下一个节点 */
} dictEntry;
下面是hash表存储的示例图,来自《redis设计与实现》一书
以上是hash表的相关结构图。下面来看“字典”的结构图
typedef struct dict {
dictType *type; /* 类型 */
void *privdata; /* 私有数据 */
dictht ht[2]; /* 两个hash表 */
long rehashidx; /* 通过这个值判断是否需要rehash */
unsigned long iterators; /* 当前运行的迭代器的数量 */
} dict;
下面一一解释dict结构中的内容:
dictType:
typedef struct dictType {
uint64_t (*hashFunction)(const void *key); /* 计算hash值的函数 */
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;
ht[2]:一个字典结构中包含两个hash表,一般情况下字典只会使用ht[0]的hash表,而空余的ht[1]作为备用表,会在rehash的时候使用。
rehashidx:这是一个在rehash操作中至关重要的属性,如果rehashidx=-1,那么不会进行rehash,否则,进行rehash。
在这一版的dict中还有一个dictIterator结构,它的作用也可以参考Java的hashmap中的迭代器
typedef struct dictIterator {
dict *d; /* 指向一个字典的指针 */
long index;
/* 当safe=1时,说明迭代器安全,那么可以调用有关dict的一系列函数对dict进行CRUD;
* 当safe!=1时,说明迭代器不安全,只能调用dictNext()函数,遍历下一个节点
*/
/* table是字典结构中所包含的两个hash表的下标,用来表示此时是ht[0]还是ht[1] */
int table, safe;
dictEntry *entry, *nextEntry; /* 指向当前元素和下一个元素 */
long long fingerprint; /* 指纹,当迭代器不安全时,用来检测 */
} dictIterator;
可以看看有关fingerprint的函数:可以看出fingerprint就是一个计算出的hash值。
long long dictFingerprint(dict *d) {
long long integers[6], hash = 0;
int j;
/* ht[0]hash表 */
integers[0] = (long) d->ht[0].table;
integers[1] = d->ht[0].size;
integers[2] = d->ht[0].used;
/* ht[1]hash表 */
integers[3] = (long) d->ht[1].table;
integers[4] = d->ht[1].size;
integers[5] = d->ht[1].used;
/* 以下是hash算法:Result = hash(hash(hash(int1)+int2)+int3) ...
* 这种算法的好处在于,即便是两个或多个itegers数组中的数值相同,
* 但是排序不同,也可能会使得hash的结果不同
*/
for (j = 0; j < 6; j++) {
hash += integers[j];
/* 使用64位整数hash(看不懂算了知道有这个东西就行) */
hash = (~hash) + (hash << 21); // hash = (hash << 21) - hash - 1;
hash = hash ^ (hash >> 24);
hash = (hash + (hash << 3)) + (hash << 8); // hash * 265
hash = hash ^ (hash >> 14);
hash = (hash + (hash << 2)) + (hash << 4); // hash * 21
hash = hash ^ (hash >> 28);
hash = hash + (hash << 31);
}
return hash;
}