[置顶] 【Redis源码剖析】 - Redis内置数据结构之字典dict

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

今天我们来讲讲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
    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].table[idx] = he->next;
                if (!nofree) {
                    dictFreeKey(d, he);
                    dictFreeVal(d, he);
                }
                zfree(he);
                d->ht[table].used--;
                return DICT_OK;
            }
            prevHe = he;
            he = he->next;
        }
        // 如果没有执行rehash操作,全部元素都在ht[0]中,不需要再找ht[1]
        if (!dictIsRehashing(d)) break;
    }
    return DICT_ERR; /* not found */
}

/* dictGenericDelete()函数的包装 */
int dictDelete(dict *ht, const void *key) {
    return dictGenericDelete(ht,key,0);
}

/* dictGenericDelete()函数的包装 */
int dictDeleteNoFree(dict *ht, const void *key) {
    return dictGenericDelete(ht,key,1);
}

/* Destroy an entire dictionary */
/* 销毁整个字典dict */
int _dictClear(dict *d, dictht *ht, void(callback)(void *)) {
    unsigned long i;

    /* Free all the elements */
    /* 释放全部键值对 */
    for (i = 

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
1、 2、 3、 4、 5、 6、 7、 8、 9、 10、 11、
目录 介绍 LAMP环境搭建 打开UCloud防火墙 WordPress安装 应用测试 介绍 本篇博客旨在通过介绍搭建一个WordPress博客的过程介绍在UCloud的云主机(UHOST)上搭建单机Web服务的过程。WordPress作为一个著名的CMS系统,有着广泛的应用。其作为博客也是非常常见的用法。所以这里使用WordPress作为示例软件来说明。在UHost上安装LAMP环境和在其他的linux下安装过程类似,但是要 注意UCloud平台提供的防火墙,记得开放相应端口 。 LAMP环境搭建 在U

maven+spring+springmvc+mybatis整合 - 2016-03-31 18:03:16

开发环境:eclipse-mars2(自带maven) 1,创建工程file-new-maven project-next 选择webapp-next 填写项目名字-finish 此时的项目前有红色X,项目结构也有问题,需要处理一下,选中项目右键-properties-resource-utf-8 apply ,选择java build path-jre-edit-勾选workspace defult……,apply-finish 还报错,继续,打开pom.xml,加入 !-- 导入java ee jar
1、         配置Tomcat 配置Tomcat所在路径 配置Tomcat使用JDK版本 如果Tomcat为7.0则添加Tomcat-juli.jar包 2、         new一个web project。 2、右键项目,为项目添加Struts支持。   点击Finish。src目录下多了struts.xml配置文件。   3、使用MyEclipse DataBase Explorer建立数据源。   new一个数据源。填入数据源信息。   点击test Driver,如果成功显示:   点击

处理器CPU概念及CPU多线程 - 2016-03-31 18:03:31

1 socket, core, thread (1)socket就是主板上插cpu的槽的数目,也即管理员说的”路“     芯片厂商会把一个或多个Core封装在一个chip上,称作Socket(插槽)。假设一个插槽有两个Core,主板上插2个插槽,就是4核系统。 (2)core就是我们平时说的”核“,即双核,4核等。单核(single-core)和多核(multi-core)也称作uniprocessor和multiprocessor (3)thread就是每个core的硬件线程数,即超线程 具体例子,某

IO-Polling的代码分析 - 2016-03-31 17:03:48

在前一篇文章《 IO-Polling实现分析与性能评测 》中提到了IO-Polling与中断的原理区别,并通过两种模式下NVMe SSD的性能测试对两者进行了对比。这篇文章将深入到IO-Polling的代码层面,对这一IO处理模式进行一个解读。 IO-Polling模式已经加入了linux 4.4的内核,并已有多个成员组在测试IO-Polling对快速设备的性能影响。目前的IO-Polling仅支持direct-IO的sync模式读写操作,后期将加入对libaio的IO-Polling的支持,详细见下图g

spring+dubbo整合 - 2016-03-31 17:03:41

创建公共接口或者工程用到的一些bean,我这里就只是创建了一个接口。工程目录如下: DemoService接口的代码如下: spanpackage com.sw.www;public interface DemoService {public void sayHello(); }/span 将上面的接口工程打包为一个jar给服务提供方和消费方公用,创建服务提供方工程,工程目录如下: 其中DemoServiceImpl实现了公共接口。而后期服务消费方不需要关心它是如何实现的。其代码如下: spanpacka

使用Spring Boot创建微服务 - 2016-03-31 17:03:35

过去几年以来,“微服务架构”的概念已经在软件开发领域获得了一个稳定的基础。作为“面向服务架构”(SOA)的一个继任者,微服务同样也可以被归类为“分布式系统”这一类,并且进一步发扬了SOA中的许多概念与实践。不过,它们在不同之处在于每个单一服务所应承担的责任范围。在SOA中,每个服务将负责处理广范围的功能与数据领域,而微服务的一种通用指南则认为,它所负责的部分是管理一个单独的数据领域,以及围绕着该领域的相关功能。使用分布式系统方式的目的是将整体性的服务基础设施解耦为个别的可扩展子系统,可以通过垂直分片的方式
     最近碰到了一个比较奇怪的数据库连接问题。问题的起因是做一个数据整合的时候,把服务器B的防火墙信息都拷贝到了服务器A,迁移的过程都很顺利,是一套开发测试环境,迁移完成之后,从应用的反馈来说都没有发现问题,过了几天有个开发的同事找到我说,她现在连接数据库的时候总是有超时的错误。之前连接服务器B是没有问题的,想让我帮她看看。    对于这个问题,最直接的思路就是防火墙了,确认客户端IP,端口库,数据库实例名都没有问题,但是她那边的反馈就是怎么都连接不了。而且比较奇怪的是和她一个组的另外一个同事连接就没有
putty.exe —— 是一个Telnet、SSH、rlogin、纯TCP以及串行接口连接软件。此处主要用来连接linux,执行linux命令,重启tomcat等。 flashfxp.exe        ——   文件传输工具,主要通过putty把windows上面编译好的class文件、web(js,css,jsp/html)、web.xml放到linux服务器上 。                       一、更新svn代码 邮件项目名称——Team——更新     二、与资源库同步核对