Redis的字典(Dictionary)实现是基于哈希表的数据结构,并采用了渐进式rehash(重新哈希)机制来保证性能。Redis字典主要用于实现数据库键空间、哈希类型等。下面详细介绍Redis字典的实现,包括关键结构体定义、主要操作函数及其实现细节。
主要结构体
dictEntry:字典中的每个条目(键值对)。dictht:哈希表,包含哈希表数组和大小信息。dict:字典结构,包含两个哈希表和rehash状态。
结构体定义
c
#include <stdlib.h>
#include <string.h>
// 字典条目
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
// 哈希表
typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表大小
unsigned long sizemask; // 哈希表大小掩码,用于计算索引
unsigned long used; // 已使用的节点数
} dictht;
// 字典
typedef struct dict {
dictType *type; // 操作函数
void *privdata; // 私有数据
dictht ht[2]; // 两个哈希表
long rehashidx; // rehash 索引,-1 表示没有进行 rehash
unsigned long iterators; // 当前激活的迭代器数量
} dict;
字典的主要操作
1. 初始化
字典的初始化包括创建哈希表并设置默认值。
c
int dictInit(dict *d, dictType *type, void *privDataPtr) {
_dictReset(&d->ht[0]);
_dictReset(&d->ht[1]);
d->type = type;
d->privdata = privDataPtr;
d->rehashidx = -1;
d->iterators = 0;
return DICT_OK;
}
static void _dictReset(dictht *ht) {
ht->table = NULL;
ht->size = 0;
ht->sizemask = 0;
ht->used = 0;
}
2. 插入
插入操作首先会检查是否需要进行rehash,然后根据键计算哈希值和索引,将键值对插入到哈希表中。
c
int dictAdd(dict *d, void *key, void *val) {
dictEntry *entry = dictAddRaw(d, key);
if (!entry) return DICT_ERR;
dictSetVal(d, entry, val);
return DICT_OK;
}
dictEntry *dictAddRaw(dict *d, void *key) {
int index;
dictht *ht;
if (dictIsRehashing(d)) _dictRehashStep(d);
// 计算索引
if ((index = _dictKeyIndex(d, key)) == -1) return NULL;
// 选择哈希表(ht[0] 为当前使用表,rehashing 时 ht[1] 为新表)
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
// 创建新条目
dictEntry *entry = zmalloc(sizeof(*entry));
entry->next = ht->table[index];
ht->table[index] = entry;
ht->used++;
// 设置键
dictSetKey(d, entry, key);
return entry;
}
int _dictKeyIndex(dict *d, const void *key) {
unsigned int h, idx, table;
dictEntry *he;
// 计算哈希值
h = dictHashKey(d, key);
// 检查是否需要 rehash
if (dictIsRehashing(d)) _dictRehashStep(d);
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 -1;
he = he->next;
}
if (!dictIsRehashing(d)) break;
}
return idx;
}
3. 查找
查找操作根据键计算哈希值和索引,从哈希表中找到对应的条目。
c
dictEntry *dictFind(dict *d, const void *key) {
if (d->ht[0].used == 0 && d->ht[1].used == 0) return NULL;
if (dictIsRehashing(d)) _dictRehashStep(d);
unsigned int h = dictHashKey(d, key);
for (unsigned int table = 0; table <= 1; table++) {
unsigned int idx = h & d->ht[table].sizemask;
dictEntry *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;
}
4. 删除
删除操作将对应的条目从哈希表中移除。
c
int dictDelete(dict *d, const void *key) {
if (d->ht[0].used == 0 && d->ht[1].used == 0) return DICT_ERR;
if (dictIsRehashing(d)) _dictRehashStep(d);
unsigned int h = dictHashKey(d, key);
for (unsigned int table = 0; table <= 1; table++) {
unsigned int idx = h & d->ht[table].sizemask;
dictEntry *he = d->ht[table].table[idx];
dictEntry *prevHe = NULL;
while (he) {
if (dictCompareKeys(d, key, he->key)) {
if (prevHe) {
prevHe->next = he->next;
} else {
d->ht[table].table[idx] = he->next;
}
dictFreeKey(d, he);
dictFreeVal(d, he);
zfree(he);
d->ht[table].used--;
return DICT_OK;
}
prevHe = he;
he = he->next;
}
if (!dictIsRehashing(d)) return DICT_ERR;
}
return DICT_ERR;
}
5. Rehash
rehash 过程通过将条目从旧哈希表迁移到新哈希表,来逐渐完成哈希表的扩展或收缩。
c
void _dictRehashStep(dict *d) {
if (d->iterators != 0) return;
dictEntry *entry, *nextEntry;
if (d->rehashidx == -1) return;
while (d->ht[0].table[d->rehashidx] == NULL) {
d->rehashidx++;
if (d->rehashidx >= (signed)d->ht[0].size) {
_dictRehash(d, 1);
}
}
entry = d->ht[0].table[d->rehashidx];
while (entry) {
unsigned int h = dictHashKey(d, entry->key) & d->ht[1].sizemask;
nextEntry = entry->next;
entry->next = d->ht[1].table[h];
d->ht[1].table[h] = entry;
d->ht[0].used--;
d->ht[1].used++;
entry = nextEntry;
}
d->ht[0].table[d->rehashidx] = NULL;
d->rehashidx++;
if (d->ht[0].used == 0) {
zfree(d->ht[0].table);
d->ht[0] = d->ht[1];
_dictReset(&d->ht[1]);
d->rehashidx = -1;
}
}
示例使用
下面是一个简单的Java示例,展示如何使用类似Redis的字典数据结构实现基本的插入和查找功能。为了简化实现,不包含rehash逻辑。
java
import java.util.Arrays;
class DictEntry {
Object key;
Object value;
DictEntry next;
DictEntry(Object key, Object value) {
this.key = key;
this.value = value;
}
}
class Dict {
private static final int INIT_SIZE = 4;
private DictEntry[] table;
private int size;
private int sizemask;
private int used;
public Dict() {
this.table = new DictEntry[INIT_SIZE];
this.size = INIT_SIZE;
this.sizemask = INIT_SIZE - 1;
this.used = 0;
}
private int hashFunction(Object key) {
return key.hashCode() & sizemask;
}