Redis源码阅读笔记-字典结构

本文详细介绍了Redis中字典的实现原理及其底层哈希表的运作机制,包括渐进式rehash的过程、字典的扩展与收缩条件等。

字典

字典在Redis中主要用在数据库和哈希键功能上,其他也有。

当一个哈希键包含的键值对比较多,又或者键值对中的元素都是比较长的字符串时,Redis就会使用字典作为哈希键的底层实现。(《Redis设计与实现》)

特点

来之《Redis设计与实现》

  • Redis中的字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。
  • 当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash2算法来计算键的哈希值。
  • 哈希表使用链地址法来解决键冲突,被分配到同一个索引上的多个键值对会连接成一个单向链表。
  • 在对哈希表进行扩展或者收缩操作时,程序需要将现有哈希表包含的所有键值对rehash到新哈希表里面,并且这个rehash过程并不是一次性地完成的,而是渐进式地完成的。

rehash(重新散列)

来之《Redis设计与实现》

rehash的原因

随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让哈希表的负载因子(loadfactor)维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。

哈希表的扩展/收缩条件

哈希表负载因子计算公式:

负载因子 = 哈希表已经保存节点数量/哈希表大小
# load_factor = ht[0].used / ht[0].size
扩展

当以下条件任意一个被满足,Redis会自动对哈希表进行扩展操作:

  • 服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1。
  • 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5。

BGSAVE命令或者BGREWRITEAOF期间,提高扩展的负载因子是因为,在这两个命令期间,过程中,Redis需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制(copy-on-write)技术来优化子进程的使用效率,提高扩展的负载因子可以可以避免不必要的内存写入操作,最大限度地节约内存。

收缩

当哈希表的负载因子小于0.1时,Redis会自动对哈希表进行收缩操作。

rehash的步骤
  1. 为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(也即是ht[0].used属性的值):

    • 如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2的2^n(2的n次方幂);
    • 如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0].used的2^n。
  2. 将保存在ht[0]中的所有键值对rehash到ht[1]上面:rehash指的是重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。

  3. 当ht[0]包含的所有键值对都迁移到了ht[1]之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。

渐进式rehash

来之《Redis设计与实现》

rehash的动作并不是一次性,集中式地完成的,而是分多次、渐进式地完成。

原因

如果哈希表中保存的键值对数量太大,要一次性将ht[0]中的键值对全部rehash到ht[1]的话,可能会导致服务器在一段时间内停止服务。

步骤
  1. 为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表。
  2. 在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash工作正式开始。
  3. 在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序将rehashidx属性的值增一。
  4. 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这时程序将rehashidx属性的值设为-1,表示rehash操作已完成。

PS: 在渐进rehash期间,字典的删除(delete)、查找(find)、更新(update)等操作,都会现在ht[0]进行,没找到的话再在ht[1]上进行。

PPS: 另外,在渐进式rehash执行期间,新添加到字典的键值对一律会被保存到ht[1]里面,而ht[0]则不再进行任何添加操作,这一措施保证了ht[0]包含的键值对数量会只减不增,并随着rehash操作的执行而最终变成空表。

代码结构

// dict.h

// 哈希表节点
typedef struct dictEntry {
    void *key; // 键
    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    
    // 指向下一个哈希表节点,形成链表
    // 用以解决键冲突
    struct dictEntry *next;
} dictEntry;

/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
// 哈希表的结构定义
typedef struct dictht {
    dictEntry **table; // 哈希表的数组
    unsigned long size; // 哈希表的大小
    unsigned long sizemask; // 哈希表大小掩码,用于计算索引,值为 size-1
    unsigned long used; // 哈希表已有节点的数量
} dictht;

// 字典结构
typedef struct dict {
    dictType *type; // 类型特定函数,保存了用于操作特定类型键值对的函数
    void *privdata; // 私有数据,保存了需要传给类型特定函数的可选参数
    dictht ht[2]; // 哈希表,一般情况下字典只是用ht[0],ht[1]只会在进行rehash时使用
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */ // 当rehash不在进行时,值为 -1
    unsigned long iterators; /* number of iterators currently running */ // 字典上正在执行的迭代器数量
} dict;

// 记录操作字典类型特定函数的结构体
typedef struct dictType {
    uint64_t (*hashFunction)(const void *key); // 计算哈希值得函数
    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;

/* If safe is set to 1 this is a safe iterator, that means, you can call
 * dictAdd, dictFind, and other functions against the dictionary even while
 * iterating. Otherwise it is a non safe iterator, and only dictNext()
 * should be called while iterating. */
// 字典迭代器
typedef struct dictIterator {
    dict *d; // 指向指点
    long index;
    int table, safe; // 标记是否安全迭代,如果是1,则是安全迭代,可以同时执行dictAdd, dictFind等操作
    dictEntry *entry, *nextEntry; // 下一个哈希节点的指针
    /* unsafe iterator fingerprint for misuse detection. */
    long long fingerprint;
} dictIterator;

部分代码解析

  • dict *dictCreate(dictType *type, void *privDataPtr)初始化创建一个字典,type为类型操作函数的集合,privDataPtr为操作函数所需要的参数:

    	// dict.c
    
    	/* Create a new hash table */
    	dict *dictCreate(dictType *type,
    	        void *privDataPtr)
    	{
    	    // 为字典申请内存
    	    dict *d = zmalloc(sizeof(*d));
    
    	    // 对字典d进行初始化
    	    _dictInit(d,type,privDataPtr);
    	    return d;
    	}
    
    	/* Initialize the hash table */
    	// 字典初始化函数
    	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;
    	}
    
    	/* Reset a hash table already initialized with ht_init().
    	 * NOTE: This function should only be called by ht_destroy(). */
    	// 将重置哈希表的数据
    	static void _dictReset(dictht *ht)
    	{
    	    ht->table = NULL;
    	    ht->size = 0;
    	    ht->sizemask = 0;
    	    ht->used = 0;
    	}
    
  • int dictAdd(dict *d, void *key, void *val) 将键keyval加入到字典d中:

    	/* Add an element to the target hash table */
    	// 往字典d中添加键值对key, val,成功返回DICT_OK,失败返回DICT_ERR
    	// 如果key已经存在,将会返回DICT_ERR
    	int dictAdd(dict *d, void *key, void *val)
    	{
    	    // 将 key 添加到字典中,并返回对应的哈希表节点
    	    dictEntry *entry = dictAddRaw(d,key,NULL);
    
    	    if (!entry) return DICT_ERR;
    
    	    // 将 val 添加到对应的哈希表节点当中
    	    dictSetVal(d, entry, val);
    	    return DICT_OK;
    	}
    
    	/* Low level add or find:
    	 * This function adds the entry but instead of setting a value returns the
    	 * dictEntry structure to the user, that will make sure to fill the value
    	 * field as he wishes.
    	 *
    	 * This function is also directly exposed to the user API to be called
    	 * mainly in order to store non-pointers inside the hash value, example:
    	 *
    	 * entry = dictAddRaw(dict,mykey,NULL);
    	 * if (entry != NULL) dictSetSignedIntegerVal(entry,1000);
    	 *
    	 * Return values:
    	 *
    	 * If key already exists NULL is returned, and "*existing" is populated
    	 * with the existing entry if existing is not NULL.
    	 *
    	 * If key was added, the hash entry is returned to be manipulated by the caller.
    	 */
    	// 底层接口
    	// 往字典d中添加键值对key, val
    	// 如果返回结果是NULL,表示添加失败;成功添加将会返回对应key的节点
    	// 如果key已经存在于字典,也会返回NULL,但是会将已经存在的节点写入"*existing"中
    	dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
    	{
    	    long index;
    	    dictEntry *entry;
    	    dictht *ht;
    
    	    // 如果字典正在渐进rehash,执行一步rehash动作
    	    if (dictIsRehashing(d)) _dictRehashStep(d);
    
    	    /* Get the index of the new element, or -1 if
    	     * the element already exists. */
    	    // 计算出 key 的 hash值,并获取对应的索引位置
    	    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
    	        return NULL;
    
    	    /* Allocate the memory and store the new entry.
    	     * Insert the element in top, with the assumption that in a database
    	     * system it is more likely that recently added entries are accessed
    	     * more frequently. */
    	    // 如果字典正在渐进 rehash ,则使用ht[1],否则使用 ht[0]
    	    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    	    // 为hash表节点申请内存
    	    entry = zmalloc(sizeof(*entry));
    	    // 将 新hash表节点entry的next指针 指向 hash表中index位置
    	    // 防止键冲突
    	    entry->next = ht->table[index];
    	    // 将 hash表中的index位置 指向 新的hash表节点entry
    	    ht->table[index] = entry;
    	    // 节点加1
    	    ht->used++;
    
    	    /* Set the hash entry fields. */
    	    // 将 key 设入到 节点entry 中
    	    dictSetKey(d, entry, key);
    	    return entry;
    	}
    
    	/* Returns the index of a free slot that can be populated with
    	 * a hash entry for the given 'key'.
    	 * If the key already exists, -1 is returned
    	 * and the optional output parameter may be filled.
    	 *
    	 * Note that if we are in the process of rehashing the hash table, the
    	 * index is always returned in the context of the second (new) hash table. */
    	//  通过参数`hash`获取一个索引位置
    	// 如果key已经存在,将会返回 -1, 同时已经存在的节点写入"*existing"中
    	static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
    	{
    	    unsigned long idx, table;
    	    dictEntry *he;
    	    if (existing) *existing = NULL;
    
    	    /* Expand the hash table if needed */
    	    // 通过_dictExpandIfNeeded()做hash表扩展检查
    	    if (_dictExpandIfNeeded(d) == DICT_ERR)
    	        return -1;
    	    for (table = 0; table <= 1; table++) {
    	        // 在两个表ht[0], ht[1]中查找
    
    	        // 通过hash表大小掩码 和 key的hash值, 计算出索引 idx
    	        idx = hash & d->ht[table].sizemask;
    	        /* Search if this slot does not already contain the given key */
    	        // 在该索引 idx 下,检查key是否已经存在
    	        he = d->ht[table].table[idx];
    	        while(he) {
    	            if (key==he->key || dictCompareKeys(d, key, he->key)) {
    	                // 如果key已经存在,返回-1,且将节点写入"*existing"中
    	                if (existing) *existing = he;
    	                return -1;
    	            }
    	            he = he->next;
    	        }
    	        // 如果不是正在rehash表,是不用检查ht[1]的
    	        if (!dictIsRehashing(d)) break;
    	    }
    	    return idx;
    	}
    
    	/* Expand the hash table if needed */
    	// 如果有需要,对字典进行扩展
    	static 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. */
    	    // 如果默认hash表 ht[0] 是空的,则扩展为默认大小
    	    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
    
    	    /* If we reached the 1:1 ratio, and we are allowed to resize the hash
    	     * table (global setting) or we should avoid it but the ratio between
    	     * elements/buckets is over the "safe" threshold, we resize doubling
    	     * the number of buckets. */
    	    // 如果哈希表的已用节点数 >= 哈希表的大小,即负载因子大于1,
    	    // 并且以下条件任一个为真:
    	    //  1) dict_can_resize 为真(应该是系统设置是否允许字典改变大小的标识位)
    	    //  2) d->ht[0].used/d->ht[0].size > dict_force_resize_ratio(dict_force_resize_ratio的值为5,表示负载因子大于5)
    	    // 则调用dictExpand 函数对hash表进行扩展
    
    	    if (d->ht[0].used >= d->ht[0].size &&
    	        (dict_can_resize ||
    	         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
    	    {
    	        // // 扩展大小为已使用节点数的2倍
    	        return dictExpand(d, d->ht[0].used*2);
    	    }
    	    return DICT_OK;
    	}
    
  • int dictExpand(dict *d, unsigned long size) 扩展或者创建hash表到指定的size大小:

    	/* Expand or create the hash table */
    	int dictExpand(dict *d, unsigned long size)
    	{
    	    // 新的hash表n
    	    dictht n; /* the new hash table */
    	    // 通过_dictNextPower()获取实际的大小,因为参数传入的size可能不是一个2的幂
    	    unsigned long realsize = _dictNextPower(size);
    
    	    /* the size is invalid if it is smaller than the number of
    	     * elements already inside the hash table */
    	    // 如果 字典d 正在rehash
    	    // 或者
    	    // d->ht[0]已经使用的节点数大于指定的size
    	    // 将会返回错误
    	    if (dictIsRehashing(d) || d->ht[0].used > size)
    	        return DICT_ERR;
    
    	    /* Rehashing to the same table size is not useful. */
    	    // hash表d->ht[0]的大小已经是将要扩展的实际大小,无需扩展,返回错误
    	    if (realsize == d->ht[0].size) return DICT_ERR;
    
    	    /* Allocate the new hash table and initialize all pointers to NULL */
    	    // 为新的 hash表n 赋值
    	    n.size = realsize;
    	    n.sizemask = realsize-1;
    	    // 申请相应的内存
    	    n.table = zcalloc(realsize*sizeof(dictEntry*));
    	    n.used = 0;
    
    	    /* Is this the first initialization? If so it's not really a rehashing
    	     * we just set the first hash table so that it can accept keys. */
    	    // 如果 hash表d->ht[0].table 是NULL的,直接将d->ht[0]指向 n
    	    if (d->ht[0].table == NULL) {
    	        d->ht[0] = n;
    	        return DICT_OK;
    	    }
    
    	    /* Prepare a second hash table for incremental rehashing */
    	    // 将 d->ht[1] 指向 n,并将 d->rehashidx 设置为0,表明 字典d 要rehash
    	    d->ht[1] = n;
    	    d->rehashidx = 0;
    	    return DICT_OK;
    	}
    
    	/* Our hash table capability is a power of two */
    	// 计算hash表的实际大小,大小是一个2的幂
    	static unsigned long _dictNextPower(unsigned long size)
    	{
    	    // 默认最小的hash表大小
    	    // DICT_HT_INITIAL_SIZE(4)
    	    unsigned long i = DICT_HT_INITIAL_SIZE;
    
    	    // LONG_MAX 是long的最大数
    	    // 如果指定的size >= LONG_MAX
    	    // 将会返回 LONG_MAX + 1UL的大小
    	    if (size >= LONG_MAX) return LONG_MAX + 1LU;
    	    // 每次 i * 2,直至i刚好大于或等于参数size
    	    while(1) {
    	        if (i >= size)
    	            return i;
    	        i *= 2;
    	    }
    	}
    
  • int dictRehash(dict *d, int n)渐进式rehash函数,d是要rehash的字典,n是要执行的步数:

    	/* Performs N steps of incremental rehashing. Returns 1 if there are still
    	 * keys to move from the old to the new hash table, otherwise 0 is returned.
    	 *
    	 * Note that a rehashing step consists in moving a bucket (that may have more
    	 * than one key as we use chaining) from the old to the new hash table, however
    	 * since part of the hash table may be composed of empty spaces, it is not
    	 * guaranteed that this function will rehash even a single bucket, since it
    	 * will visit at max N*10 empty buckets in total, otherwise the amount of
    	 * work it does would be unbound and the function may block for a long time. */
    	// 渐进式rehash的步骤,执行 参数n 步,
    	// 返回1表示还有key需要从旧hash表(d->ht[0])移动到新hash表(d->ht[1])
    	// 返回0表示已经完成
    	// bucket表示哈希表一个槽位
    	int dictRehash(dict *d, int n) {
    
    	    // 访问的空bucket的最大数量
    	    int empty_visits = n*10; /* Max number of empty buckets to visit. */
    	    if (!dictIsRehashing(d)) return 0;
    
    	    // 循环,直至n到0,或者哈希表ht[0]中没有节点
    	    while(n-- && d->ht[0].used != 0) {
    	        dictEntry *de, *nextde;
    
    	        /* Note that rehashidx can't overflow as we are sure there are more
    	         * elements because ht[0].used != 0 */
    	        // 断言判断 哈希表的大小ht[0].size 要大于 rehashidx
    	        // rehashidx是用来记录 rehash 在 ht[0] 中,操作到哪个位置
    	        assert(d->ht[0].size > (unsigned long)d->rehashidx);
    	        while(d->ht[0].table[d->rehashidx] == NULL) {
    	            // 当遇到空的bucket,rehashidx自增1
    	            d->rehashidx++;
    	            // 如果 访问空bucket次数empty_visits 达到了0,返回1
    	            // 表示还需要继续rehash
    	            if (--empty_visits == 0) return 1;
    	        }
    	        de = d->ht[0].table[d->rehashidx];
    	        /* Move all the keys in this bucket from the old to the new hash HT */
    	        while(de) {
    	        // 当de是不为空的时候,将该bucket下所有的hash节点移到ht[1]
    	            uint64_t h;
    
    	            nextde = de->next;
    	            /* Get the index in the new hash table */
    	            h = dictHashKey(d, de->key) & d->ht[1].sizemask;
    	            de->next = d->ht[1].table[h];
    	            d->ht[1].table[h] = de;
    	            d->ht[0].used--;
    	            d->ht[1].used++;
    	            de = nextde;
    	        }
    	        // 将 ht[0].table[d->rehashidx] 指向 NULL
    	        // 因为如果 ht[0].table[d->rehashidx] 有节点,在上面循环中已将移动到了ht[1]
    	        d->ht[0].table[d->rehashidx] = NULL;
    	        // rehashidx自增1
    	        d->rehashidx++;
    	    }
    
    	    /* Check if we already rehashed the whole table... */
    	    // 检查是否将整个ht[0]的节点转移到了ht[1]
    	    // 如果是,则rehash完成,函数返回0
    	    if (d->ht[0].used == 0) {
    	        zfree(d->ht[0].table);
    	        d->ht[0] = d->ht[1];
    	        _dictReset(&d->ht[1]);
    	        d->rehashidx = -1;
    	        return 0;
    	    }
    
    	    /* More to rehash... */
    	    return 1;
    	}
    

字典API

参考之《Redis设计与实现》

函数作用
dict *dictCreate(dictType *type, void *privDataPtr)初始化创建一个字典,type为类型操作函数的集合,privDataPtr为操作函数所需要的参数
int dictExpand(dict *d, unsigned long size)扩展或者创建hash表到指定的size大小
int dictAdd(dict *d, void *key, void *val)将键keyval加入到字典d
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)往字典d中添加键值对key, val;如果返回结果是NULL,表示添加失败;成功添加将会返回对应key的节点;如果key已经存在于字典,也会返回NULL,但是会将已经存在的节点写入*existing
dictEntry *dictAddOrFind(dict *d, void *key)在字典d添加或者查找key,如果已经存在则返回已经存在的节点,如果不存在则创建一个新节点并加入字典d
int dictReplace(dict *d, void *key, void *val)在字典d中使用值val添加或者覆盖键key;如果键key已经存在则返回0;如果是新加入的键则返回1;
int dictDelete(dict *d, const void *key)在字典d中删除键key,如果键存在且删除成功则返回DICT_OK(0),如果键不存在则返回DICT_ERR(1)
dictEntry *dictUnlink(dict *ht, const void *key)在字典d中删除key,但是不释放该hash节点,而是将其返回。如果key不存在,则返回NULL
void dictFreeUnlinkedEntry(dict *d, dictEntry *he)释放字典d中的 已经取消绑定的hash节点he,调用前应该先调用dictUnlink()
void dictRelease(dict *d)清除并释放字典d
dictEntry * dictFind(dict *d, const void *key)在字典d中按key查找节点,并将节点返回
void *dictFetchValue(dict *d, const void *key)在字典d中按key查找节点,并返回节点中的值
int dictResize(dict *d)调整字典d的大小,使得负载因子 <= 1 (尽量接近)
dictIterator *dictGetIterator(dict *d)获得字典d的迭代器
dictIterator *dictGetSafeIterator(dict *d)获得一个安全的字典d的迭代器
dictEntry *dictNext(dictIterator *iter)使用迭代器iter获取下一个hash节点
void dictReleaseIterator(dictIterator *iter)释放迭代器iter
dictEntry *dictGetRandomKey(dict *d)随机获取字典d中的一个节点
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count)在字典d中随机获取指定个数count的节点,并存进**des中(函数不保证**des中的节点个数一定满足count,实际的节点个数是函数的返回结果)
void dictGetStats(char *buf, size_t bufsize, dict *d)调试用,取得dict当前状态,会将结果写入buf
uint64_t dictGenHashFunction(const void *key, int len)计算key的hash值
uint64_t dictGenCaseHashFunction(const unsigned char *buf, int len)不区分大小写计算key的hash值
void dictEmpty(dict *d, void(callback)(void*))清空字典d并在完成后调用回调函数
void dictEnableResize(void)打开dict调整大小开关,一个全局设置,将全局变量dict_can_resize设为1
void dictDisableResize(void)关闭dict调整大小开关,一个全局设置,将全局变量dict_can_resize设为0
int dictRehash(dict *d, int n)渐进式rehash函数,对字典d执行n步渐进式rehash
int dictRehashMilliseconds(dict *d, int ms)对字典d执行指定毫秒ms的rehash
void dictSetHashFunctionSeed(uint8_t *seed)设置hash种子
uint8_t *dictGetHashFunctionSeed(void)取得hash种子
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata)遍历字典d
uint64_t dictGetHash(dict *d, const void *key)在字典d中获取/计算key的hash值
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash)根据指针*oldptr和hash值hash在字典d中查找hash节点

转载于:https://my.oschina.net/jianming/blog/2032304

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值