Redis(129)Redis的字典(Dictionary)是如何实现的?

Redis的字典(Dictionary)实现是基于哈希表的数据结构,并采用了渐进式rehash(重新哈希)机制来保证性能。Redis字典主要用于实现数据库键空间、哈希类型等。下面详细介绍Redis字典的实现,包括关键结构体定义、主要操作函数及其实现细节。

主要结构体

  1. dictEntry:字典中的每个条目(键值对)。
  2. dictht:哈希表,包含哈希表数组和大小信息。
  3. 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;
    }

  
相关推荐
Victor3562 小时前
Redis(128)Redis的跳表(Skip List)是如何实现的?
后端
q***92519 小时前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
m0_6398171510 小时前
基于springboot火锅店管理系统【带源码和文档】
java·spring boot·后端
会编程的林俊杰10 小时前
SpringBoot项目启动时的依赖处理
java·spring boot·后端
码事漫谈11 小时前
C++循环结构探微:深入理解while与do...while
后端
码事漫谈11 小时前
现代C++:一场静默的革命,告别“C with Classes”
后端
AntBlack12 小时前
AI Agent : CrewAI 简单使用 + 尝试一下股票分析
后端·python·ai编程
刘一说12 小时前
深入理解 Spring Boot 单元测试:从基础到最佳实践
spring boot·后端·单元测试