Redis底层数据结构——Dict

redis底层数据结构
Redis底层数据结构——SDS
Redis底层数据结构——IntSet



一、Dict介绍

redis 中的 dict 是一个哈希表,用来存储键值类型的数据。redis的数据类型中用到dict的有:

  • Set:key用来存储元素,value均为null。
  • Zset
  • Hash:默认采用跳表,当跳表中的元素数量超过hash-max-ziplist-entries(512) 且 任意entry大小超过了hash-max-ziplist-value(64) 时,采用dict。可通过config get hash-max-ziplist-entriesconfig get hash-max-ziplist-value来查看这两个值。

二、Dict源码分析

1. dict结构体

// dict.h
struct dict {
    // dictType定义了字典操作类型,包括键值的复制、释放、比较和哈希函数
    dictType *type;

    // hash表,为了在rehashing过程中同时使用旧表和新表
    dictEntry **ht_table[2];
    /* 记录两个哈希表中已使用的桶数量,也就是键值对数量
     * ht_used[0]表示主哈希表中存储的键值对数量
     * ht_used[1]表示rehash过程中临时哈希表中存储的键值对数量 */
    unsigned long ht_used[2];
    /* rehashing进度的索引
     * 如果rehashidx等于-1,表示没有进行rehash
     * 如果rehashidx大于等于0,表示正在rehash,且该索引前面的键值对都已经迁移到新的哈希表*/
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */

    /* Keep small vars at end for optimal (minimal) struct padding */
    unsigned pauserehash : 15; /* If >0 rehashing is paused */
    // 标记是否使用存储的键
    unsigned useStoredKeyApi : 1; /* See comment of storedHashFunction above */
    // 有符号数组,表示ht_table的大小指数,ht_table的实际大小是1 << ht_size_exp,也就是预分配的键值对数量
    signed char ht_size_exp[2]; /* exponent of size. (size = 1<<exp) */
    // 用于控制是否暂停自动调整大小,如果大于0,则自动调整大小被暂停
    int16_t pauseAutoResize;  /* If >0 automatic resizing is disallowed (<0 indicates coding error) */
    void *metadata[];
};

这里还是得提一下ht_used和ht_size_exp,redis每次给dict分配的键值对数量都是2的指数幂,最少为4个,这个指数幂就是ht_size_exp。ht_used是dict实际使用的键值对数量。

2. dictType结构体

// dict.h
typedef struct dictEntry dictEntry; /* opaque */
typedef struct dict dict;

typedef struct dictType {
    /* Callbacks */
    uint64_t (*hashFunction)(const void *key); // 哈希函数,用于生成键的哈希值。
    void *(*keyDup)(dict *d, const void *key); // 复制键的回调函数。当需要深拷贝键时使用,返回新分配的键的副本。
    void *(*valDup)(dict *d, const void *obj); // 复制值的回调函数,与 keyDup 类似,用于深拷贝值。
    int (*keyCompare)(dict *d, const void *key1, const void *key2);// 键比较函数,用于判断两个键是否相等。
    void (*keyDestructor)(dict *d, void *key); // 键销毁函数,在字典删除键时调用,用于释放键占用的内存或执行相关清理操作。
    void (*valDestructor)(dict *d, void *obj); // 值销毁函数,与 keyDestructor 类似,用于清理值。
    int (*resizeAllowed)(size_t moreMem, double usedRatio); // 判断是否允许调整字典大小(扩容或缩容)。根据字典大小和负载因子动态调整。
    /* Invoked at the start of dict initialization/rehashing (old and new ht are already created) */
    void (*rehashingStarted)(dict *d); // 在初始化或 rehashing 开始时调用的回调函数,用于执行一些额外操作。
    /* Invoked at the end of dict initialization/rehashing of all the entries from old to new ht. Both ht still exists
     * and are cleaned up after this callback.  */
    void (*rehashingCompleted)(dict *d); // rehashing 完成时调用的回调函数。
    /* Allow a dict to carry extra caller-defined metadata. The
     * extra memory is initialized to 0 when a dict is allocated. */
    size_t (*dictMetadataBytes)(dict *d); // 返回字典元数据所需的字节数,用于动态分配额外内存以存储元信息。

    /* Data */
    void *userdata;

    /* Flags */
    /* 如果设置了“no_value”标志,则表示不使用值,即字典是一个Set。
     * 当设置此标志时,无法访问字典项的值,也无法使用“dictSetKey()”函数。
     * Entry元数据也不能使用。*/
    unsigned int no_value:1;
    /* 如果 no_value 等于 1 并且所有键的低位位(LSB)等于 1,将 keys_are_odd 设置为 1 可以启用另一个优化:
     * 存储一个没有分配 dictEntry 的键 */
    unsigned int keys_are_odd:1;
    /* TODO: Add a 'keys_are_even' flag and use a similar optimization if that
     * flag is set. */
    uint64_t (*storedHashFunction)(const void *key); // 为存储的键提供专门的哈希函数。用于某些场景下,存储键和查找键的类型不同的情况(例如存储结构体、查找字符串)。
    int (*storedKeyCompare)(dict *d, const void *key1, const void *key2); // 为存储的键提供专门的比较函数,用于与 storedHashFunction 类似的场景。

    /* Optional callback called when the dict is destroyed. */
    void (*onDictRelease)(dict *d); // 字典销毁时调用的回调函数,用于释放资源或执行清理任务。
} dictType;

dicttype主要有以下作用:

  • 灵活操作键值:可以通过自定义哈希函数、比较函数和复制/销毁函数,使字典支持任意类型的键值对。
  • 集合支持(no_value 标志):当字典仅存储键(例如Set)时,使用 no_value 标志禁用值相关的操作,从而节省内存和提升效率。
  • 存储键和查找键分离:storedHashFunction 和 storedKeyCompare 提供了存储键和查找键分离的机制。适用于复杂场景,例如存储结构体键但用字符串查找。
  • 动态扩展:通过 dictMetadataBytes 支持元数据扩展。可通过用户数据字段(userdata)添加额外功能。

3. dictEntry结构体

struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;     /* Next entry in the same hash bucket. */
};

dict中维护了两个哈希表ht_table,这个ht_table是个二维数组,其中存储的每个元素都是dictEntry指针。

从dictEntry中的union可知,redis的哈希表的value类型可以是任意类型指针、无符号整数、有符号整数以及双精度小数。如果要存放字符串,这里的指针类型就为sds *。

next指向下一个dictEntry结构体,用来处理哈希冲突。

整个dict的内存布局如下:
在这里插入图片描述

4. dict扩容

当dict中元素数量满足以下两种情况时,dict发生扩容:

  • 如果dict_can_resize被设置为DICT_RESIZE_ENABLE,也就是可以调整哈希表大小,且哈希表中的已存储的键值对数量 超过了 哈希表预分配的数量,则扩容;
  • 如果dict_can_resize没有被设置为DICT_RESIZE_FORBID,也就是可以调整哈希表大小或避免调整哈希表大小(DICT_RESIZE_FORBID) 且 哈希表中的已存储的键值对数量 超过了 哈希表预分配的数量的四倍,则扩容。PS:这种情况一般发生在需要一次性插入大量的键值对时。
// dict.c
int 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,则将哈希表扩展到初始大小:4
    if (DICTHT_SIZE(d->ht_size_exp[0]) == 0) {
        dictExpand(d, DICT_HT_INITIAL_SIZE);
        return DICT_OK;
    }
    
    /* 以下两种情况会进行扩容:
     * 1. 字典可以resize标记被置为DICT_RESIZE_ENABLE 且 该dict已存储的键值对数量超过已分配的键值对数量
     * 2. 字典可以resize标记没有被置为DICT_RESIZE_FORBID,也就是处于DICT_RESIZE_AVOID 或 DICT_RESIZE_ENABLE 且
     *    该dict已存储的键值对数量 超过了 4倍的已分配的键值对数量
     * */
    if ((dict_can_resize == DICT_RESIZE_ENABLE &&
         d->ht_used[0] >= DICTHT_SIZE(d->ht_size_exp[0])) ||
        (dict_can_resize != DICT_RESIZE_FORBID &&
         d->ht_used[0] >= dict_force_resize_ratio * DICTHT_SIZE(d->ht_size_exp[0])))
    {
        //每新增一个键值对,就需要判断一下是否需要重新分配内存大小
        if (dictTypeResizeAllowed(d, d->ht_used[0] + 1))
            dictExpand(d, d->ht_used[0] + 1);
        return DICT_OK;
    }
    return DICT_ERR;
}

关于dict_can_resize的解释如下,英语好的可自行翻译:

/* Using dictSetResizeEnabled() we make possible to disable
 * resizing and rehashing of the hash table as needed. This is very important
 * for Redis, as we use copy-on-write and don't want to move too much memory
 * around when there is a child performing saving operations.
 *
 * Note that even when dict_can_resize is set to DICT_RESIZE_AVOID, not all
 * resizes are prevented:
 *  - A hash table is still allowed to expand if the ratio between the number
 *    of elements and the buckets >= dict_force_resize_ratio.
 *  - A hash table is still allowed to shrink if the ratio between the number
 *    of elements and the buckets <= 1 / (HASHTABLE_MIN_FILL * dict_force_resize_ratio). */
static dictResizeEnable dict_can_resize = DICT_RESIZE_ENABLE;
static unsigned int dict_force_resize_ratio = 4;

为什么需要控制哈希表的 resize?

  1. 性能影响:哈希表的扩容或缩容涉及重新分配内存迁移现有的键值对(rehashing)。这会对性能产生显著影响。
    特别是在执行 保存(saving)操作 时(如 RDB 快照或 AOF 持久化),Redis 使用 写时复制(Copy-On-Write, COW),数据迁移会增加内存页面的写操作,显著增加内存消耗。
  2. 行为控制:为了减少在这些情况下对性能和内存的影响,redis 允许通过 dictSetResizeEnabled() 接口动态控制哈希表的 resize 行为。

dict_can_resize 是一个全局变量,控制哈希表是否允许 resize,它有以下几种可能的值:

  1. DICT_RESIZE_FORBID(完全禁止)。
  2. DICT_RESIZE_AVOID(尽量避免)。默认情况下,尽量避免扩展和缩减,但在以下条件下仍允许:
    • 扩展:当元素数与桶数的比例(load factor)大于等于 dict_force_resize_ratio(默认值为 4)。这里的桶数就是键值对的数量。
    • 缩减:当 load factor 小于等于 1 / (HASHTABLE_MIN_FILL * dict_force_resize_ratio)(默认值为 1 / 32,因为 HASHTABLE_MIN_FILL = 8)。也就是哈希表中实际使用的键值对数量连预分配的数量的32分之一都不到。
  3. DICT_RESIZE_ENABLE(允许调整大小).

5. dict缩容

与dict扩容类似,当dict中元素数量满足以下两种情况时,dict发生缩容:

  • 如果dict_can_resize被设置为DICT_RESIZE_ENABLE,也就是可以调整哈希表大小,且哈希表中的已存储的键值对数量 不超过 哈希表预分配的数量的八分之一,则缩容;
  • 如果dict_can_resize没有被设置为DICT_RESIZE_FORBID,也就是可以调整哈希表大小或避免调整哈希表大小(DICT_RESIZE_FORBID) 且 哈希表中的已存储的键值对数量 不超过 哈希表预分配的数量的三十二分之一,则缩容。
// dict.h
int dictShrinkIfNeeded(dict *d) {
    /* Incremental rehashing already in progress. Return. */
    if (dictIsRehashing(d)) return DICT_OK;
    
    /* If the size of hash table is DICT_HT_INITIAL_SIZE, don't shrink it. */
    if (DICTHT_SIZE(d->ht_size_exp[0]) <= DICT_HT_INITIAL_SIZE) return DICT_OK;

    /* If we reached below 1:8 elements/buckets ratio, and we are allowed to resize
     * the hash table (global setting) or we should avoid it but the ratio is below 1:32,
     * we'll trigger a resize of the hash table. */
    /* 以下两种情况会进行缩容:
     * 1. 字典可以resize标记被置为DICT_RESIZE_ENABLE 且 该dict已存储的键值对数量小于已分配的键值对数量的8分之一
     * 2. 字典可以resize标记没有被置为DICT_RESIZE_FORBID,也就是处于DICT_RESIZE_AVOID 或 DICT_RESIZE_ENABLE 且
     *    该dict已存储的键值对数量 小于 已分配的键值对数量的32分之一。*/
    if ((dict_can_resize == DICT_RESIZE_ENABLE &&
         d->ht_used[0] * HASHTABLE_MIN_FILL <= DICTHT_SIZE(d->ht_size_exp[0])) ||
        (dict_can_resize != DICT_RESIZE_FORBID &&
         d->ht_used[0] * HASHTABLE_MIN_FILL * dict_force_resize_ratio <= DICTHT_SIZE(d->ht_size_exp[0])))
    {
        if (dictTypeResizeAllowed(d, d->ht_used[0]))
            dictShrink(d, d->ht_used[0]);
        return DICT_OK;
    }
    return DICT_ERR;
}

6. dict调整大小

功能:不管是扩容还是缩容,dict用到的调整大小的函数都是_dictResize,该函数实现方式是创建一个新的哈希表,并将旧的哈希表中的所有元素重新哈希到新表中,以适应字典大小的变化 。

参数:

  • d:dict
  • size:如果是扩容,size等于dict已存储的键值对数量 + 1;如果是缩容,size就等于dict已存储的键值对大小。
  • malloc_failed:标记是否由于内存分配失败导致 resize 无法完成。
int _dictResize(dict *d, unsigned long size, int* malloc_failed)
{
   
    if (malloc_failed) *malloc_failed = 0;

    /* We can't rehash twice if rehashing is ongoing. */
    assert(!dictIsRehashing(d));

    /* the new hash table */
    dictEntry **new_ht_table;
    unsigned long new_ht_used;
    // 找到第一个大于等于size的2^n,也就是新的哈希表大小指数。举例说明:
    // 如果当前哈希表存储了12个键值对,那么第一个大于等于size的2的幂就是16,因此new_ht_size_exp = 4
    signed char new_ht_size_exp = _dictNextExp(size);

    /* Detect overflows */
    // 哈希表的新大小
    size_t newsize = DICTHT_SIZE(new_ht_size_exp);
    /* 如果新的哈希表大小 < 原来哈希表的大小 或 扩容后的哈希表大小超过了size_t类型能够表示的最大值,则调整大小失败。
     * 解释:当newsize * sizeof(dictEntry*)超过size_t类型能表示的最大值时,会发生溢出,由于size_t是无符号的,
     * 溢出会导致导致结果绕回到size_t能表示的最小值,这样乘积就小于newsize了*/
    if (newsize < size || newsize * sizeof(dictEntry*) < newsize)
        return DICT_ERR;

    /* Rehashing to the same table size is not useful. */
    // 如果新旧哈希表的大小相同,则不需要调整大小
    if (new_ht_size_exp == d->ht_size_exp[0]) return DICT_ERR;

    /* Allocate the new hash table and initialize all pointers to NULL */
    if (malloc_failed) {
        new_ht_table = ztrycalloc(newsize*sizeof(dictEntry*));
        *malloc_failed = new_ht_table == NULL;
        if (*malloc_failed)
            return DICT_ERR;
    } else
        //给新哈希表分配内存
        new_ht_table = zcalloc(newsize*sizeof(dictEntry*));

    // 初始化新的哈希表
    new_ht_used = 0;

    /* Prepare a second hash table for incremental rehashing.
     * We do this even for the first initialization, so that we can trigger the
     * rehashingStarted more conveniently, we will clean it up right after. */
    d->ht_size_exp[1] = new_ht_size_exp;
    d->ht_used[1] = new_ht_used;
    d->ht_table[1] = new_ht_table;
    d->rehashidx = 0;
    if (d->type->rehashingStarted) d->type->rehashingStarted(d);

    /* Is this the first initialization or is the first hash table empty? If so
     * it's not really a rehashing, we can just set the first hash table so that
     * it can accept keys. */
    /* 判断是否是首次初始化(ht_table[0] == NULL)或当前第一个哈希表为空(ht_used[0] == 0)。
     * 如果是首次初始化或当前哈希表为空,则直接设置哈希表的第一个表(ht_table[0]),而无需进行rehash*/
    if (d->ht_table[0] == NULL || d->ht_used[0] == 0) {
        if (d->type->rehashingCompleted) d->type->rehashingCompleted(d);
        if (d->ht_table[0]) zfree(d->ht_table[0]);
        d->ht_size_exp[0] = new_ht_size_exp;
        d->ht_used[0] = new_ht_used;
        d->ht_table[0] = new_ht_table;
        _dictReset(d, 1);
        d->rehashidx = -1;
        return DICT_OK;
    }

    return DICT_OK;
}

7. rehash

哈希表的大小通常是 2 的幂,因此,redis在计算桶的索引时,使用dictht_size_mask来限制哈希值的范围。通过与dictht_size_mask进行位与(&)运算,redis能确保桶索引始终处于合法的范围内,即小于哈希表的大小。

当dict发生扩容和缩容时,ht_table的大小会发生改变,而dictht_size_mask的值等于size - 1,此时key的哈希值会失效,因此需要进行再哈希(rehash)操作。

其步骤为:

  1. 计算新hash表的realsize,值取决于当前要做的是扩容还是缩容:
    • 如果是扩容,则新size为第一个大于等于dict->ht_used[0]+1的2^n
    • 如果是缩容,则新size为第一个大于等于dict->ht_used[0]的2^n,且不得小于4
  2. 按照新的realSize申请内存空间,创建ht_table,并赋值给dict->ht_table[1]
  3. 设置dict->rehashidx=0,标示开始rehash
  4. 将dict->ht_table[0]中的每一个dictEntry都rehash到dict.ht_table[1]
  5. 将dict.ht_table[1]赋值给dict.ht[0],给dict.ht_table[1]初始化为空哈希表,释放原来的dict.ht_table[0]的内存

当dict中有数百万的entry时,如果一次性完成上面的操作,极有可能导致主线程阻塞。因此dict的rehash是分多次、渐进式地完成。

渐进式 rehash
从上面第3步开始,实际上做的是:

  1. 每次执行增、删、改、查时,都检查一下dict.rehashidx是否大于-1,如果是,则将d->ht_table[0][d->rehashidx]位置处的entry链表rehash到dict.ht[1],然后将rehashidx++,直到dict.ht_table[0]的所有数据都rehash到dict.ht_table[1]。
  2. 将dict.ht_table[1]赋值给dict.ht[0],给dict.ht[1]初始化为空哈希表,释放原来的dict.ht[0]的内存
  3. 将rehashidx赋值为-1,代表rehash结束
  4. 在rehash的过程中,新增操作,直接将数据写入到ht_table[1],查、删、改则会在dict.ht_table[0]和dict.ht_table[1]依次查找并执行。这样可以确保ht_table[0]的数据只减不增,随着rehash最终为空。
// dict.c
// n:每次要迁移的键值对个数
int dictRehash(dict *d, int n) {
    int empty_visits = n*10; /* Max number of empty buckets to visit. */
    unsigned long s0 = DICTHT_SIZE(d->ht_size_exp[0]);
    unsigned long s1 = DICTHT_SIZE(d->ht_size_exp[1]);
    if (dict_can_resize == DICT_RESIZE_FORBID || !dictIsRehashing(d)) return 0;
    /* If dict_can_resize is DICT_RESIZE_AVOID, we want to avoid rehashing. 
     * - If expanding, the threshold is dict_force_resize_ratio which is 4.
     * - If shrinking, the threshold is 1 / (HASHTABLE_MIN_FILL * dict_force_resize_ratio) which is 1/32. */
    if (dict_can_resize == DICT_RESIZE_AVOID && 
        ((s1 > s0 && s1 < dict_force_resize_ratio * s0) || //对应扩容情况
         (s1 < s0 && s0 < HASHTABLE_MIN_FILL * dict_force_resize_ratio * s1))) //对应缩容情况
    {
        return 0;
    }

    while(n-- && d->ht_used[0] != 0) {
        /* Note that rehashidx can't overflow as we are sure there are more
         * elements because ht[0].used != 0 */
        assert(DICTHT_SIZE(d->ht_size_exp[0]) > (unsigned long)d->rehashidx);
        while(d->ht_table[0][d->rehashidx] == NULL) {
            d->rehashidx++;
            if (--empty_visits == 0) return 1;
        }
        /* Move all the keys in this bucket from the old to the new hash HT */
        rehashEntriesInBucketAtIndex(d, d->rehashidx);
        d->rehashidx++;
    }

    return !dictCheckRehashingCompleted(d);
}

8. 时间复杂度

增:平均时间复杂度为O(1)。考虑最坏情况,也就是发生扩容,此时需要将原来的数据从ht_table[0]拷贝到ht_table[1],这个过程的时间复杂度为O(N)。
删:平均时间复杂度为O(1)。考虑最坏情况,哈希表中存放的键全部发生了哈希冲突,此时哈希表退化至链表,时间复杂度为O(N)。
改:平均时间复杂度为O(1)。考虑最坏情况,哈希表中存放的键全部发生了哈希冲突,此时哈希表退化至链表,时间复杂度为O(N)。
查:平均时间复杂度为O(1)。考虑最坏情况,哈希表中存放的键全部发生了哈希冲突,此时哈希表退化至链表,时间复杂度为O(N)。

三、一些函数和自定义宏

1. 自定义宏

// dict.h
#define DICTHT_SIZE(exp) ((exp) == -1 ? 0 : (unsigned long)1<<(exp)) // 计算dict键值对数量,2^exp个
#define DICTHT_SIZE_MASK(exp) ((exp) == -1 ? 0 : (DICTHT_SIZE(exp))-1) //掩码,值为dict键值对数量-1
### Redis底层实现的数据结构详解 Redis 是一种高性能的内存数据库,其底层数据结构的设计直接影响到它的性能表现。以下是 Redis 底层主要使用的几种核心数据结构及其作用。 #### 1. `redisObject` 结构 Redis 中的对象通过 `redisObject` 进行封装,这种设计使得 Redis 能够灵活地支持多种类型的值[^1]。 - **字段说明**: - `type`: 表示对象的具体类型(如字符串、列表、集合等),占用 4 位。 - `encoding`: 表示对象的实际存储方式(如整数、压缩列表、哈希表等),也占用 4 位。 - `lru`: 记录对象最近一次被访问的时间戳,用于 LRU 缓存淘汰策略。 - `refcount`: 引用计数器,记录当前对象被引用的次数。 - `ptr`: 指向实际存储数据的指针,具体指向的内容取决于 `type` 和 `encoding` 的组合。 #### 2. 列表键的底层实现 当一个列表键包含较少的元素,并且这些元素是小整数值或者较短的字符串时,Redis 使用压缩列表作为底层实现[^3]。随着版本更新,最新的 Redis 已经将压缩列表替换为更高效的 `listpack` 数据结构。对于较大的列表,则采用双向链表进行存储。 #### 3. 哈希键的底层实现 Redis 的哈希键有两种可能的底层实现形式: - 如果哈希键较小,Redis 可能会选择使用 `ziplist` 或者新的 `listpack` 来节省空间[^2]。 - 对于较大规模的哈希键,Redis 使用哈希表来提高查询效率。哈希表由 `dictht` 定义,其中包含了两个重要的部分——散列函数和冲突处理机制[^4]。 ##### 关于哈希表的细节 - **`dictht` 结构**: 描述了一个具体的哈希表实例,包括以下几个重要成员变量: - `table[]`: 存储哈希表节点的数组。 - `size`: 数组的最大容量。 - `sizemask`: 掩码值,用来快速定位索引位置,总是等于 `size - 1`。 - `used`: 当前已经分配并正在使用的槽位数量。 - **`dictEntry` 结构**: 单个哈希表条目对应的结构体,保存了键值对以及解决碰撞所需的链接信息。 - `key`: 键名。 - `v.val`, `v.u64`, `v.s64`: 多种可选的值表示方法。 - `next`: 链接至同一桶内的其他节点,构成单向链表以应对 hash 冲突的情况[^5]。 #### 4. 字符串键的底层实现 Redis 的字符串是最基础也是最常用的一种数据类型,在内部它既可以是一个简单的 C-style string (char array),也可以是以共享整数的形式存在,或者是基于 SDS (Simple Dynamic String) 实现的大尺寸动态字符串。 --- ### 示例代码展示 以下展示了如何创建一个基本的 Redis 哈希表: ```c #include <stdio.h> #include "dict.h" int main() { dictType ht; dict *d = dictCreate(&ht, NULL); /* 添加一些键值对 */ dictAdd(d, "key1", "value1"); dictAdd(d, "key2", "value2"); printf("Dictionary has %lu elements\n", d->ht[0].used); return 0; } ``` 此代码片段仅作示意用途,实际应用需引入完整的 Redis 源码库文件。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值