Redis源码剖析 - Redis内置数据结构之字典dict

Posted Fred^_^

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis源码剖析 - Redis内置数据结构之字典dict相关的知识,希望对你有一定的参考价值。

原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51018337

Redis源码剖析系列文章汇总:传送门

今天我们来讲讲Redis中的哈希表。哈希表在C++中对应的是map数据结构,但在Redis中称作dict(字典)。Redis只是用了几个简单的结构体和几种常见的哈希算法就实现了一个简单的类似高级语言中的map结构。下面我们来具体分析一下dict的实现。


在学习数据结构的时候,我们接触过一种称作“散列表”的结构,可以根据关键字而直接访问记录。说的具体一点就是通过把key值映射到表中的一个位置来访问,从而加快查找速度。Redis中的dict数据结构和我们之前学过的“散列表”大同小异。总结如下:

1、dict的结构

Redis定义了dictEntry、dictType、dictht和dict四个结构体来实现散列表的功能。它们具体定义如下:

(1)dictEntry结构体

/* 保存键值(key - value)对的结构体,类似于STL的pair。*/
typedef struct dictEntry 
    // 关键字key定义
    void *key;  
    // 值value定义,只能存放一个被选中的成员
    union 
        void *val;      
        uint64_t u64;   
        int64_t s64;    
        double d;       
     v;
    // 指向下一个键值对节点
    struct dictEntry *next;
 dictEntry;

从dictEntry的定义我们也可以看出dict通过“拉链法”来解决冲突问题。

(2)、dictType结构体

/* 定义了字典操作的公共方法,类似于adlist.h文件中list的定义,将对节点的公共操作方法统一定义。搞不明白为什么要命名为dictType */
typedef struct dictType 
    /* hash方法,根据关键字计算哈希值 */
    unsigned int (*hashFunction)(const void *key);
    /* 复制key */
    void *(*keyDup)(void *privdata, const void *key);
    /* 复制value */
    void *(*valDup)(void *privdata, const void *obj);
    /* 关键字比较方法 */
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    /* 销毁key */
    void (*keyDestructor)(void *privdata, void *key);
    /* 销毁value */
    void (*valDestructor)(void *privdata, void *obj);
 dictType;

(3)、dictht结构体

/* 哈希表结构 */
typedef struct dictht 
    // 散列数组。
    dictEntry **table;
    // 散列数组的长度
    unsigned long size;
    // sizemask等于size减1
    unsigned long sizemask;
    // 散列数组中已经被使用的节点数量
    unsigned long used;
 dictht;

(4)、dict结构体

/* 字典的主操作类,对dictht结构再次包装  */
typedef struct dict 
    // 字典类型
    dictType *type;
    // 私有数据
    void *privdata;
    // 一个字典中有两个哈希表
    dictht ht[2];
    // 数据动态迁移的下标位置
    long rehashidx; 
    // 当前正在使用的迭代器的数量
    int iterators; 
 dict;

这四个结构体之间的关系如下:

2、散列函数

Redis提供了三种不同的散列函数,分别是:

(1)、使用Thomas Wang’s 32 bit Mix哈希算法,对一个整型进行哈希,该方法在dictIntHashFunction函数中实现。
(2)、使用MurmurHash2哈希算法对字符串进行哈希,该方法在dictGenHashFunction函数中实现。
(3)、在dictGenCaseHashFunction函数中提供了一种比较简单的哈希算法,对字符串进行哈希

上面这三种方法的实现具体可以参考下面的代码。至于这几种方法的优劣,这里就不展开讲了(我自己也不是很清楚),大家可以自行google一下。

3、Rehash操作

Rehash是dict一个很重要的操作。在前面我们看到dict结构体中有两个哈希表(定义为dictht ht[2])。通常情况下,dict中的所有数据(键值对)都存放在ht[0],但随着dict中数据量的增加需要进行扩容操作(为什么?数据越多,冲突的元素越多,ht[0]中的链表越长,查找效率越低),此时就需要进行rehash。dict在rehash的时先申请一个更大的空间并用ht[1]指向该空间,然后把ht[0]中的所有数据rehash到ht[1]中,数据迁移完毕后在将ht[1]赋值给ht[0]并清空ht[1]。如果这个rehash过程是一次性完成的倒是很好理解,但从其源码中我们可以看出:dict的rehash操作并不是一次性完成的,而是分成多步。具体来说dict提供了两种不同的策略:一种是每次将ht[0]中的一个bucket,也就是散列数组中对应的一个链表中的所有数据进行rehash到ht[1]中,对应的函数是_dictRehashStep。另一种是每次执行一段固定的时间,时间到了就暂停rehash操作,对应的函数是dictRehashMilliseconds。

_dictRehashStep和dictRehashMilliseconds底层都调用了dictRehash函数来进行rehash操作。实现如下:

/* 执行n步渐进式的rehash操作,如果还有key需要从旧表迁移到新表则返回1,否则返回0 */
int dictRehash(dict *d, int n) 
    if (!dictIsRehashing(d)) return 0;

    // n步渐进式的rehash操作就是每次只迁移哈希数组中的n个bucket
    while(n--) 
        dictEntry *de, *nextde;

        /* Check if we already rehashed the whole table... */
        // 检查是否对整个哈希表进行了rehash操作
        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;
        

        /* Note that rehashidx can't overflow as we are sure there are more
         * elements because ht[0].used != 0 */
        // rehashidx标记的是当前rehash操作进行到了ht[0]旧表的那个位置(下标),因此需要判断它是否操作ht[0]的长度
        assert(d->ht[0].size > (unsigned long)d->rehashidx);
        // 跳过ht[0]中前面为空的位置
        while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
        // 得到对应的链表
        de = d->ht[0].table[d->rehashidx];
        /* Move all the keys in this bucket from the old to the new hash HT */
        // 下面的操作将每个节点(键值对)从ht[0]迁移到ht[1],此过程需要重新计算每个节点key的哈希值
        while(de) 
            unsigned int 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;
        
        d->ht[0].table[d->rehashidx] = NULL;
        d->rehashidx++;
    
    return 1;

dictRehash函数执行N步rehash操作。每步中,首先判断是否已经完成了rehashN操作,判断的标准就是ht[0]的used是否为0。ht[0]的used == 0说明ht[0]中所有的链表都是空的,那么所有的元素都已经移动到ht[1]中。此时,将ht[1]赋值给ht[0],清空ht[1]。将rehashidx赋值为-1表明rehash结束。如果rehash没有结束,那么,查找ht[0]中下一个非空的桶。将这个桶中的所有数据rehash到ht[1]中。

既然rehash操作是分步进行的,那dict就可能存在这样一种状态:既有数据存放在ht[0]中,又有数据存放在ht[1]中。这样在对dict进行访问的时候就要加以区别。对于dictFind、dictDelete函数,需要遍历ht[0]和ht[1]两个表,对于dictAdd函数则需要根据当前dict是否在执行rehash操作来决定将键值对插入ht[0]还是ht[1]中。在下面的源码中,我们可以看到这些函数的具体实现。

dict的注意点大概就是这些,其它的都比较简单就直接贴代码了。大部分代码我都写了注释,如下:

dict.h头文件:

/* Hash Tables Implementation.
 *
 * This file implements in-memory hash tables with insert/del/replace/find/
 * get-random-element operations. Hash tables will auto-resize if needed
 * tables of power of two in size are used, collisions are handled by
 * chaining. See the source code for more information... :)
 *
 * Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Redis nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdint.h>

#ifndef __DICT_H
#define __DICT_H

// 成功 or 出错
#define DICT_OK 0
#define DICT_ERR 1

/* Unused arguments generate annoying warnings... */
#define DICT_NOTUSED(V) ((void) V)

/* 保存键值(key - value)对的结构体,类似于STL的pair。Redis采用拉链法处理冲突,
    会把冲突的dictEntry组成一个链表。
*/
typedef struct dictEntry 
    // 关键字key定义
    void *key;  
    // 值value定义,这里采用了联合体,根据union的特点,联合体只能存放一个被选中的成员
    union 
        void *val;      // 自定义类型
        uint64_t u64;   // 无符号整形
        int64_t s64;    // 有符号整形
        double d;       // 浮点型
     v;
    // 指向下一个键值对节点
    struct dictEntry *next;
 dictEntry;

/* 定义了字典操作的公共方法,类似于adlist.h文件中list的定义,将对节点的公共操作方法统一定义。搞不明白为什么要命名为dictType */
typedef struct dictType 
    /* hash方法,根据关键字计算哈希值 */
    unsigned int (*hashFunction)(const void *key);
    /* 复制key */
    void *(*keyDup)(void *privdata, const void *key);
    /* 复制value */
    void *(*valDup)(void *privdata, const void *obj);
    /* 关键字比较方法 */
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    /* 销毁key */
    void (*keyDestructor)(void *privdata, void *key);
    /* 销毁value */
    void (*valDestructor)(void *privdata, void *obj);
 dictType;

/* 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 *类型,所以这里是指针数组。
    dictEntry **table;
    // 散列数组的长度
    unsigned long size;
    // sizemask等于size减1,给定 key 的哈希值计算出来之后,就会和这个数值进行 & 操作,决定元素被放到 table 数组的那一个位置上。
    unsigned long sizemask;
    // 散列数组中已经被使用的节点数量
    unsigned long used;
 dictht;

/* 字典的主操作类,对dictht结构再次包装  */
typedef struct dict 
    // 字典类型
    dictType *type;
    // 私有数据指针
    void *privdata;
    // 一个字典中有两个哈希表,后面的分析中,我们将ht[0]称作就表,ht[1]称作新表
    /*  dict的rehash。通常情况下,所有的数据都是存在放dict的ht[0]中,ht[1]只在rehash的时候使用。
        dict进行rehash操作的时候,将ht[0]中的所有数据rehash到ht[1]中。然后将ht[1]赋值给ht[0],
        并清空ht[1]。rehash操作我们会在后面详细看到。
    */
    dictht ht[2];
    // 数据动态迁移的位置,如果rehashidx == -1说明没有rehash过
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    // 当前正在使用的迭代器的数量
    int iterators; /* number of iterators currently running */
 dict;

/* 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. */
 /* dict的迭代器定义,该迭代器有安全和不安全之分,这个跟dict的rehash操作有关,我们后面会详细说 */
typedef struct dictIterator 
    /* 当前使用的字典dict */
    dict *d;
    /* 当前迭代器下标 */
    long index;
    /* table指示字典中散列表下标,safe指明该迭代器是否安全 */
    int table, safe;
    /* 键值对节点指针 */
    dictEntry *entry, *nextEntry;
    /* unsafe iterator fingerprint for misuse detection. */
    long long fingerprint;
 dictIterator;

/* 遍历回调函数 */
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);

/* This is the initial size of every hash table */
/* 散列数组的初始长度 */
#define DICT_HT_INITIAL_SIZE     4

/* 下面是节点(键值对)操作的宏定义 */

/* ------------------------------- Macros ------------------------------------*/
/* 释放节点value,实际上调用dictType中的valDestructor函数 */
#define dictFreeVal(d, entry) \\
    if ((d)->type->valDestructor) \\
        (d)->type->valDestructor((d)->privdata, (entry)->v.val)

/* 设置节点value */
#define dictSetVal(d, entry, _val_) do  \\
    if ((d)->type->valDup) \\
        entry->v.val = (d)->type->valDup((d)->privdata, _val_); \\
    else \\
        entry->v.val = (_val_); \\
 while(0)

/* 设置节点的值value,类型为signed int */
#define dictSetSignedIntegerVal(entry, _val_) \\
    do  entry->v.s64 = _val_;  while(0)

/* 设置节点的值value,类型为unsigned int */
#define dictSetUnsignedIntegerVal(entry, _val_) \\
    do  entry->v.u64 = _val_;  while(0)

/* 设置节点的值value,类型为double */
#define dictSetDoubleVal(entry, _val_) \\
    do  entry->v.d = _val_;  while(0)

/* 释放节点的键key */
#define dictFreeKey(d, entry) \\
    if ((d)->type->keyDestructor) \\
        (d)->type->keyDestructor((d)->privdata, (entry)->key)

/* 设置节点的键key */
#define dictSetKey(d, entry, _key_) do  \\
    if ((d)->type->keyDup) \\
        entry->key = (d)->type->keyDup((d)->privdata, _key_); \\
    else \\
        entry->key = (_key_); \\
 while(0)

// 判断两个key是否相等
#define dictCompareKeys(d, key1, key2) \\
    (((d)->type->keyCompare) ? \\
        (d)->type->keyCompare((d)->privdata, key1, key2) : \\
        (key1) == (key2))

/* 获取指定key的哈希值*/
#define dictHashKey(d, key) (d)->type->hashFunction(key)
/* 获取节点的key值*/
#define dictGetKey(he) ((he)->key)
/* 获取节点的value值 */
#define dictGetVal(he) ((he)->v.val)
/* 获取节点的value值,类型为signed int */
#define dictGetSignedIntegerVal(he) ((he)->v.s64)
/* 获取节点的value值,类型为unsigned int */
#define dictGetUnsignedIntegerVal(he) ((he)->v.u64)
/* 获取节点的value值,类型为double */
#define dictGetDoubleVal(he) ((he)->v.d)
/* 获取字典中哈希表的总长度 */
#define dictSlots(d) ((d)->ht[0].size+(d)->ht[1].size)
/* 获取字典中哈希表中已经被使用的节点数量 */
#define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)
/* 判断字典dict是否正在执行rehash操作 */
#define dictIsRehashing(d) ((d)->rehashidx != -1)

/* API */
/* 下面定义了操作字典的方法 */

// 创建一个字典dict
dict *dictCreate(dictType *type, void *privDataPtr);
// 字典容量扩充
int dictExpand(dict *d, unsigned long size);
// 根据key和value往字典中添加一个键值对
int dictAdd(dict *d, void *key, void *val);
// 往字典中添加一个只有key的dictEntry结构
dictEntry *dictAddRaw(dict *d, void *key);
int dictReplace(dict *d, void *key, void *val);
dictEntry *dictReplaceRaw(dict *d, void *key);
// 根据key删除字典中的一个键值对
int dictDelete(dict *d, const void *key);
int dictDeleteNoFree(dict *d, const void *key);
// 释放整个字典
void dictRelease(dict *d);
// 根据key在字典中查找一个键值对
dictEntry * dictFind(dict *d, const void *key);
// 根据key在字典中查找对应的value
void *dictFetchValue(dict *d, const void *key);
// 重新计算字典大小
int dictResize(dict *d);
// 获取字典的普通迭代器
dictIterator *dictGetIterator(dict *d);
// 获取字典的安全迭代器
dictIterator *dictGetSafeIterator(dict *d);
// 根据迭代器获取下一个键值对
dictEntry *dictNext(dictIterator *iter);
// 释放迭代器
void dictReleaseIterator(dictIterator *iter);
// 随机获取一个键值对
dictEntry *dictGetRandomKey(dict *d);
// 打印字典当前状态
void dictPrintStats(dict *d);
unsigned int dictGenHashFunction(const void *key, int len);
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
// 清空字典
void dictEmpty(dict *d, void(callback)(void*));
void dictEnableResize(void);
void dictDisableResize(void);
int dictRehash(dict *d, int n);
int dictRehashMilliseconds(dict *d, int ms);
void dictSetHashFunctionSeed(unsigned int initval);
unsigned int dictGetHashFunctionSeed(void);
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, void *privdata);

/* Hash table types */
/* 哈希表类型 */
extern dictType dictTypeHeapStringCopyKey;
extern dictType dictTypeHeapStrings;
extern dictType dictTypeHeapStringCopyKeyValue;

#endif /* __DICT_H */

dict.c源文件:

/* Hash Tables Implementation.
 *
 * This file implements in memory hash tables with insert/del/replace/find/
 * get-random-element operations. Hash tables will auto resize if needed
 * tables of power of two in size are used, collisions are handled by
 * chaining. See the source code for more information... :)
 *
 * Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Redis nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "fmacros.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>
#include <sys/time.h>
#include <ctype.h>

#include "dict.h"
#include "zmalloc.h"
#include "redisassert.h"

/* Using dictEnableResize() / dictDisableResize() we make possible to
 * enable/disable resizing 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 0, not all resizes are
 * prevented: a hash table is still allowed to grow if the ratio between
 * the number of elements and the buckets > dict_force_resize_ratio. */
/*通过dictEnableResize() / dictDisableResize()方法我们可以启用/禁用ht空间重新分配.  
* 这对于Redis来说很重要, 因为我们用的是写时复制机制而且不想在子进程执行保存操作时移动过多的内存. 
* 
* 需要注意的是,即使dict_can_resize设置为0, 并不意味着所有的resize操作都被禁止: 
* 一个a hash table仍然可以拓展空间,如果bucket与element数量之间的比例  > dict_force_resize_ratio。 
*/ 
static int dict_can_resize = 1;
static unsigned int dict_force_resize_ratio = 5;

/* -------------------------- private prototypes ---------------------------- */
/** 下面的方法由static修饰,是私有方法 */

// 判断字典dict是否需要扩容
static int _dictExpandIfNeeded(dict *ht);
// 由于哈希表的容量只取2的整数次幂,该函数对给定数字以2的整数次幂进行上取整
static unsigned long _dictNextPower(unsigned long size);
// 返回给定新key的对应空闲的索引地址
static int _dictKeyIndex(dict *ht, const void *key);
// 字典的初始化
static int _dictInit(dict *ht, dictType *type, void *privDataPtr);

/* -------------------------- hash functions -------------------------------- */

/* Thomas Wang's 32 bit Mix Function */
/*  Thomas Wang's 32 bit Mix哈希算法,对一个整型进行哈希。这是一种基于移位运算的哈希算法,直接根据
    给定的key值进行移位操作,具有比较高的效率
*/
unsigned int dictIntHashFunction(unsigned int key)

    key += ~(key << 15);
    key ^=  (key >> 10);
    key +=  (key << 3);
    key ^=  (key >> 6);
    key += ~(key << 11);
    key ^=  (key >> 16);
    return key;


// 哈希种子,跟产生随机数的种子类似
static uint32_t dict_hash_function_seed = 5381;

// 设置新的哈希种子
void dictSetHashFunctionSeed(uint32_t seed) 
    dict_hash_function_seed = seed;


// 获取当前哈希种子的数值
uint32_t dictGetHashFunctionSeed(void) 
    return dict_hash_function_seed;


/* MurmurHash2, by Austin Appleby
 * Note - This code makes a few assumptions about how your machine behaves -
 * 1. We can read a 4-byte value from any address without crashing
 * 2. sizeof(int) == 4
 *
 * And it has a few limitations -
 *
 * 1. It will not work incrementally.
 * 2. It will not produce the same results on little-endian and big-endian
 *    machines.
 */
 /* MurmurHash2哈希算法,我也不知道是什么东东。网上资料显示MurmurHash2主要对一个字符串进行哈希,其
    基本思想是将给定的key按每四个字符分组,每四个字符当做一个uint32_t整形进行处理。
 */
// MurmurHash2哈希算法的实现,根据key值和指定长度进行哈希
unsigned int dictGenHashFunction(const void *key, int len) 
    /* 'm' and 'r' are mixing constants generated offline.
     They're not really 'magic', they just happen to work well.  */
    uint32_t seed = dict_hash_function_seed;
    const uint32_t m = 0x5bd1e995;
    const int r = 24;

    /* Initialize the hash to a 'random' value */
    uint32_t h = seed ^ len;

    /* Mix 4 bytes at a time into the hash */
    const unsigned char *data = (const unsigned char *)key;

    while(len >= 4) 
        uint32_t k = *(uint32_t*)data;

        k *= m;
        k ^= k >> r;
        k *= m;

        h *= m;
        h ^= k;

        data += 4;
        len -= 4;
    

    /* Handle the last few bytes of the input array  */
    switch(len) 
    case 3: h ^= data[2] << 16;
    case 2: h ^= data[1] << 8;
    case 1: h ^= data[0]; h *= m;
    ;

    /* Do a few final mixes of the hash to ensure the last few
     * bytes are well-incorporated. */
    h ^= h >> 13;
    h *= m;
    h ^= h >> 15;

    return (unsigned int)h;


/* And a case insensitive hash function (based on djb hash) */
/* 一种比较简单的哈希算法,也是对字符串进行哈希的 */
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len) 
    unsigned int hash = (unsigned int)dict_hash_function_seed;

    while (len--)
        hash = ((hash << 5) + hash) + (tolower(*buf++)); /* hash * 33 + c */
    return hash;


/* ----------------------------- API implementation ------------------------- */

/* Reset a hash table already initialized with ht_init().
 * NOTE: This function should only be called by ht_destroy(). */
/* 私有方法,重置哈希表,参数是dictht哈希表结构。只能在ht_destroy()中使用 */
static void _dictReset(dictht *ht)
   // 简单地清空相应变量
    ht->table = NULL;   // 前面解释过dictht.table其实就是一个指针数组
    ht->size = 0;
    ht->sizemask = 0;
    ht->used = 0;


/* Create a new hash table */
/* 创建一个字典dict */
dict *dictCreate(dictType *type,
        void *privDataPtr)

    // 先分配内存
    dict *d = zmalloc(sizeof(*d));

    // 接着进行初始化,_dictInit是一个私有方法
    _dictInit(d,type,privDataPtr);
    return d;


/* Initialize the hash table */
// 初始化一个字典
int _dictInit(dict *d, dictType *type,
        void *privDataPtr)

    // 重置两个哈希表,一个字典dict钟有两个哈希表
    _dictReset(&d->ht[0]);
    _dictReset(&d->ht[1]);
    // 下面各个字段的含义见dict结构体的定义
    d->type = type;
    d->privdata = privDataPtr;
    d->rehashidx = -1;
    d->iterators = 0;
    return DICT_OK;


/* Resize the table to the minimal size that contains all the elements,
 * but with the invariant of a USED/BUCKETS ratio near to <= 1 */
/* 调整哈希表容量,目标是用最小的散列数组来容纳所有的键值对。类似于C++ STL 的vector::shrink_to_fit函数 */
int dictResize(dict *d)

    int minimal;

    // 如果 dict_can_resize = 0或者正在执行rehash操作,则拒绝resize操作。
    if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
    // 最少的数值就是散列数组中目前存在的键值对数量
    minimal = d->ht[0].used;
    if (minimal < DICT_HT_INITIAL_SIZE)
        minimal = DICT_HT_INITIAL_SIZE; // 防止过小
    // 调用dictExpand进行容量调整
    return dictExpand(d, minimal);


/* Expand or create the hash table */
// 字典容量扩充
int dictExpand(dict *d, unsigned long size)

    // 新的哈希表
    dictht n; /* the new hash table */
    // 哈希表的容量被设置为2的整数次幂,这里对给定的数值以2的整数次幂进行上取整
    unsigned long realsize = _dictNextPower(size);

    /* the size is invalid if it is smaller than the number of
     * elements already inside the hash table */
    // 如果字典dict正在rehash或者realsize数值不合法,拒绝expand操作
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    /* Allocate the new hash table and initialize all pointers to NULL */
    // 按指定长度重新分配一个新的哈希表
    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. */
    // 如果旧表为空,则这并不是一次rehashing操作,直接将新的哈希表赋值给旧表指针
    if (d->ht[0].table == NULL) 
        d->ht[0] = n;
        return DICT_OK;
    

    /* Prepare a second hash table for incremental rehashing */
    /* 将新创建的哈希表赋值给ht[1],rehashidx设置为0,准备进行渐进式的rehash操作 */
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;


/* 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. */
/* 执行n步渐进式的rehash操作,如果还有key需要从旧表迁移到新表则返回1,否则返回0 */
int dictRehash(dict *d, int n) 
    if (!dictIsRehashing(d)) return 0;

    // n步渐进式的rehash操作就是每次只迁移哈希数组中的n歌元素
    while(n--) 
        dictEntry *de, *nextde;

        /* Check if we already rehashed the whole table... */
        // 检查是否对整个哈希表进行了rehash操作
        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;
        

        /* Note that rehashidx can't overflow as we are sure there are more
         * elements because ht[0].used != 0 */
        // rehashidx标记的是当前rehash操作进行到了ht[0]旧表的那个位置(下标),因此需要判断它是否操作ht[0]的长度
        assert(d->ht[0].size > (unsigned long)d->rehashidx);
        // 跳过ht[0]中前面为空的位置
        while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
        de = d->ht[0].table[d->rehashidx];
        /* Move all the keys in this bucket from the old to the new hash HT */
        // 下面的操作将每个节点(键值对)从ht[0]迁移到ht[1],此过程需要重新计算每个节点key的哈希值
        while(de) 
            unsigned int 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;
        
        d->ht[0].table[d->rehashidx] = NULL;
        d->rehashidx++;
    
    return 1;


// 获取当前的时间戳(一毫秒为单位)
long long timeInMilliseconds(void) 
    struct timeval tv;

    gettimeofday(&tv,NULL);
    return (((long long)tv.tv_sec)*1000)+(tv.tv_usec/1000);


/* Rehash for an amount of time between ms milliseconds and ms+1 milliseconds */
/* 在指定的时间内执行rehash操作 */
int dictRehashMilliseconds(dict *d, int ms) 
    long long start = timeInMilliseconds();
    int rehashes = 0;

    while(dictRehash(d,100)) 
        rehashes += 100;
        if (timeInMilliseconds()-start > ms) break;
    
    return rehashes;


/* This function performs just a step of rehashing, and only if there are
 * no safe iterators bound to our hash table. When we have iterators in the
 * middle of a rehashing we can't mess with the two hash tables otherwise
 * some element can be missed or duplicated.
 *
 * This function is called by common lookup or update operations in the
 * dictionary so that the hash table automatically migrates from H1 to H2
 * while it is actively used. */
 /* 在执行查询或更新操作时,如果符合rehash条件会触发一次rehash操作,每次执行一步 */
static void _dictRehashStep(dict *d) 
    if (d->iterators == 0) dictRehash(d,1);


/* Add an element to the target hash table */
/* 往字典中添加一个新的键值对 */
int dictAdd(dict *d, void *key, void *val)

    // 往字典中添加一个只有key的dictEntry结构
    dictEntry *entry = dictAddRaw(d,key);

    // 如果entry == NULL,说明该key已经存在,添加失败
    if (!entry) return DICT_ERR;
    // 设置对应的value
    dictSetVal(d, entry, val);
    return DICT_OK;


/* Low level add. 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 user API to be called
 * mainly in order to store non-pointers inside the hash value, example:
 *
 * entry = dictAddRaw(dict,mykey);
 * if (entry != NULL) dictSetSignedIntegerVal(entry,1000);
 *
 * Return values:
 *
 * If key already exists NULL is returned.
 * If key was added, the hash entry is returned to be manipulated by the caller.
 */
 /* dictAdd的底层实现方法。往字典中添加一个只有key的dictEntry结构,如果给定的key已经存在,则返回NULL */
dictEntry *dictAddRaw(dict *d, void *key)

    int index;
    dictEntry *entry;
    dictht *ht;

    // 触发rehash操作
    if (dictIsRehashing(d)) _dictRehashStep(d);

    /* Get the index of the new element, or -1 if
     * the element already exists. */
    // 如果给定key已经存在,则操作失败直接返回NULL
    if ((index = _dictKeyIndex(d, key)) == -1)
        return NULL;

    /* Allocate the memory and store the new entry */
    // 如果字典正在rehash,则插入到新表ht[1],否则插入到旧表ht[0]
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    entry = zmalloc(sizeof(*entry));
    // 使用拉链法处理冲突
    entry->next = ht->table[index];
    ht->table[index] = entry;
    // 更新键值对数量
    ht->used++;

    /* Set the hash entry fields. */
    dictSetKey(d, entry, key);
    return entry;


/* Add an element, discarding the old if the key already exists.
 * Return 1 if the key was added from scratch, 0 if there was already an
 * element with such key and dictReplace() just performed a value update
 * operation. */
 /* 添加或替换字典中的键值对,如果返回值为1,表示执行了添加操作,如果返回值是0,表示执行了替换操作 */
int dictReplace(dict *d, void *key, void *val)

    dictEntry *entry, auxentry;

    /* Try to add the element. If the key
     * does not exists dictAdd will suceed. */
    // 现场时添加一个键值对,如果添加成功则直接返回,否则执行替换操作
    if (dictAdd(d, key, val) == DICT_OK)
        return 1;
    /* It already exists, get the entry */
    // 根据key找到键值对节点
    entry = dictFind(d, key);
    /* Set the new value and free the old one. Note that it is important
     * to do that in this order, as the value may just be exactly the same
     * as the previous one. In this context, think to reference counting,
     * you want to increment (set), and then decrement (free), and not the
     * reverse. */
    // 设置新值,释放旧值。需要考虑value是同一个的情况
    auxentry = *entry;
    dictSetVal(d, entry, val);
    dictFreeVal(d, &auxentry);
    return 0;


/* dictReplaceRaw() is simply a version of dictAddRaw() that always
 * returns the hash entry of the specified key, even if the key already
 * exists and can't be added (in that case the entry of the already
 * existing key is returned.)
 *
 * See dictAddRaw() for more information. */
 /* 该方法可以看多是dictAddRaw()的扩展方法,它重视返回一个给定值的键值对结构指针,如果不存在这样的键值对则先执行插入操作 */
dictEntry *dictReplaceRaw(dict *d, void *key) 
    dictEntry *entry = dictFind(d,key);

    return entry ? entry : dictAddRaw(d,key);


/* Search and remove an element */
/* 查找并删除给定key对应的键值对,nofree决定是否要销毁目标键值对 */
static int dictGenericDelete(dict *d, const void *key, int nofree)

    unsigned int h, idx;
    dictEntry *he, *prevHe;
    int table;

    if (d->ht[0].size == 0) return DICT_ERR; /* d->ht[0].table is NULL */
    if (dictIsRehashing(d)) _dictRehashStep(d);
    h = dictHashKey(d, key);    // 计算key对应的哈希值,以确定目标节点在散列数组的下标位置

    for (table = 0; table <= 1; table++) 
        idx = h & d->ht[table].sizemask;
        // 找到目标节点所在的链表,下面的操作就成了如何在链表中删除一个指定元素
        he = d->ht[table].table[idx];   
        prevHe = NULL;
        while(he) 
            if (dictCompareKeys(d, key, he->key)) 
                /* Unlink the element from the list */
                if (prevHe)
                    prevHe->next = he->next;
                else
                    d->ht[table]Redis源码剖析  -  Redis内置数据结构之压缩字典zipmap

Redis源码剖析 - Redis内置数据结构之双向链表list

Redis源码剖析 - Reids内置数据结构之整数集合intset

Redis源码剖析 - Reids内置数据结构之整数集合intset

Redis源码剖析 - Redis内置数据结构之字符串sds

Redis源码剖析 - Redis数据类型之redisObject