redis原理-数据结构
2016-02-17 09:30
465 查看
redis原理-数据结构
一、 内存分配
redis内存分配函数是在文件zmalloc.h和zmalloc.c里面进行声明和定义的,主要的函数如下:
void*zmalloc(size_t size);//分配内存
void*zrealloc(void *ptr, size_t size); //重分配内存
voidzfree(void *ptr);//释放内存
redis使用了zmalloc zrealloc zfree来封装了内存管理的函数,这里针对不同的平台来封装,从而屏蔽了底层的差异性实现跨平台,定义如下:
#ifdefined(USE_TCMALLOC)//如果系统存在tcmalloc
#defineZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "."__xstr(TC_VERSION_MINOR))
#include<google/tcmalloc.h>
#if(TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) ||(TC_VERSION_MAJOR > 1)
#defineHAVE_MALLOC_SIZE 1
#definezmalloc_size(p) tc_malloc_size(p)
#else
#error"Newer version of tcmalloc required"
#endif
#elifdefined(USE_JEMALLOC) //如果系统存在jemalloc
#defineZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "."__xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))
#include<jemalloc/jemalloc.h>
#if(JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) ||(JEMALLOC_VERSION_MAJOR > 2)
#defineHAVE_MALLOC_SIZE 1
#definezmalloc_size(p) je_malloc_usable_size(p)
#else
#error"Newer version of jemalloc required"
#endif
#elifdefined(__APPLE__) //如果是苹果平台
#include<malloc/malloc.h>
#defineHAVE_MALLOC_SIZE 1
#definezmalloc_size(p) malloc_size(p)
#endif
#ifndefZMALLOC_LIB
#defineZMALLOC_LIB "libc"
#endif
定义平台之间的差异,主要是tcmalloc(google)、jemalloc(facebook)、苹果平台。
上边说过,封装就是为了屏蔽底层平台的差异,同时方便自己实现相关的统计函数。具体来说就是:
若系统中存在Google的TC_MALLOC库,则使用tc_malloc一族函数代替原本的malloc一族函数。
若当前系统是Mac系统,则使用<malloc/malloc.h>中的内存分配函数。
其他情况,在每一段分配好的空间前头,同时多分配一个定长的字段,用来记录分配的空间大小。
#ifdefHAVE_MALLOC_SIZE
#definePREFIX_SIZE (0)
#else
#ifdefined(__sun) || defined(__sparc) || defined(__sparc__)
#definePREFIX_SIZE (sizeof(long long))
#else
#definePREFIX_SIZE (sizeof(size_t))
#endif
#endif
如果是sun下就使用 sizeof(long long),如果是linux就使用sizeof(size_t)了。
二、 简单字符串
适用场景:
redis默认字符串都是使用sds。
优点:
获取字符串长度:复杂度O(1),直接使用sdshdr->len就能获取到长度。而常规的是o(N)。
二进制安全存储:使用长度来规定了数据的存放长度,而c语言字符数组遇到\0就认为是结尾了,使得redis不仅仅可以存放字符串还可以存放任意二进制数据。
杜绝缓冲区溢出:sds存放的数据长度都是指定的,不存在溢出。
修改字符串减少内存重分配次数:在sdscat调用的sdsMakeRoomFor实现中当拷贝一个比当前存储区大的字符串的时候,如果小于1m会直接分配2倍当前字符串的大小空间,如果大于1m会在当前字符串长度的基础上面多分配1m的空间。
惰性释放:在sdstrim(sdsclear)中,会把删除的字符空间长度累加sdshdr->free里。
原理解释:
简单动态字符串(simpledynamic string,SDS),定义在sds.h头文件里面,包含了sds的定义以及sds相关的基础操作函数。
sds定义:
struct sdshdr {
int len;
int free;
char buf[];
};
这里面最后一个buf[]使用0其实是一种叫做柔性数组的技巧:
redis使用sds和常规的字符串好处有:
sds定义的API有:
sdsnew :创建一个给定c字符串的sds
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init); //取字符串长度
return sdsnewlen(init, initlen);
}
sds sdsnewlen(const void *init, size_tinitlen) {
struct sdshdr *sh;
if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1); //内容 头部+数据
}else {
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
if (sh == NULL) return NULL;
sh->len = initlen;
sh->free = 0;
if (initlen && init)
memcpy(sh->buf, init, initlen);
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}
sdslen :返回sds已经使用的空间字节数
这种通过sds可以直接获取到sds的头部
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
sdscat:追加一个字符串到sds尾部
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
size_t free = sdsavail(s);
size_t len, newlen;
if (free >= addlen) return s;
len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr)));
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC) // SDS_MAX_PREALLOC=1 M大小 ,如果小于1M咱就分配字符串长度的2倍
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC; //如果大于1M,咱就在当前字符串的长度再分配1m的空间
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
if (newsh == NULL) return NULL;
newsh->free = newlen - len;
return newsh->buf;
}
sds sdscatlen(sds s, const void *t, size_tlen) {
struct sdshdr *sh;
size_t curlen = sdslen(s);
s= sdsMakeRoomFor(s,len); //减少重分配
if (s == NULL) return NULL;
sh = (void*) (s-(sizeof(struct sdshdr)));
memcpy(s+curlen, t, len);
sh->len = curlen+len;
sh->free = sh->free-len; //惰性释放
s[curlen+len] = '\0';
return s;
}
sds sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
sdstrim:清除sds首尾的字符(可指定多个)
sds sdstrim(sds s, const char *cset) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
char *start, *end, *sp, *ep;
size_t len;
sp = start = s;
ep = end = s+sdslen(s)-1;
while(sp <= end && strchr(cset, *sp)) sp++;
while(ep > start && strchr(cset, *ep)) ep--;
len = (sp > ep) ? 0 : ((ep-sp)+1);
if (sh->buf != sp) memmove(sh->buf, sp, len);
sh->buf[len] = '\0';
sh->free = sh->free+(sh->len-len); //惰性释放
sh->len = len;
return s;
}
三、 双端链表
适用场景:
链表在redis应用比较广泛,列表键底层实现之一就是链表;客户端信息;
采用双端链表可以定位到头部或者尾部,每个节点的数据类型为任意数据类型,通过定义复制、释放、比较的函数指针实现了可以操作任意用户自定义数据。
原理解释:
链表节点:
typedef struct listNode {
struct listNode *prev; //前驱
struct listNode *next;//后继
void *value; //值指针
} listNode;
链表定义:
typedef struct list {
listNode *head;
listNode *tail;
// 节点值复制函数
void *(*dup)(void *ptr);
// 节点值释放函数
void (*free)(void *ptr);
// 节点值对比函数
int (*match)(void *ptr, void *key);
// 链表所包含的节点数量
unsigned long len;
} list;
/* Prototypes */
list *listCreate(void);
void listRelease(list *list);
list *listAddNodeHead(list *list, void*value);
list *listAddNodeHead(list *list, void*value)
{
listNode *node;
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
node->value = value;
if (list->len == 0) { //如果链表为空
list->head = list->tail = node;
node->prev = node->next = NULL;
}else {
node->prev = NULL;
node->next = list->head;
list->head->prev = node;
list->head = node;
}
list->len++;
return list;
}
四、 字典
适用场景:
在redis中字典用的应用很广泛的,因为redis是一个键值(k-v)内存数据库,所以存储的都是以字典作为基准的,字典也是作为hash键的底层之一:当保存的元素都是比较差的字符串或者hash键值比较多的时候就会使用字典来存储。
原理解释:
由于字典是由hash表存储的,hash表的定义如下:
typedefstruct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表大小
unsigned long sizemask; // 哈希表大小掩码,用于计算索引值,等于 size -1
unsigned long used; // 已经使用的
}dictht;
hash表节点
typedefstruct dictEntry {
void *key;
union { //是一个联合体 用于存放不同的数据类型
void *val;
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next; //主要是用于解决键冲突问题
}dictEntry;
字典类型:
typedefstruct dictType {
// hash函数
unsigned int (*hashFunction)(const void*key);
// 复制键
void *(*keyDup)(void *privdata, const void*key);
// 复制值
void *(*valDup)(void *privdata, const void*obj);
// 对比键
int (*keyCompare)(void *privdata, constvoid *key1, const void *key2);
// 销毁键
void (*keyDestructor)(void *privdata, void*key);
// 销毁值
void (*valDestructor)(void *privdata, void*obj);
}dictType;
字典的定义:
typedefstruct dict {
dictType *type; //可以实现任意用户类型,只需要设定了特定的类型操作函数
void *privdata;
dictht ht[2];
int rehashidx; /* rehashing not in progressif rehashidx == -1 */
int iterators; /* number of iteratorscurrently running */
}dict;
//其中rehash是对hash表进行增大或者减小,当hash表的负载因子(负载因子=dictht.used/dictht.size)
创建一个字典
dict*dictCreate(dictType *type, void *privDataPtr);
dict*dictCreate(dictType *type,
void *privDataPtr)
{
dict *d = zmalloc(sizeof(*d)); //字典分配内存
_dictInit(d,type,privDataPtr);
return d;
}
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; //默认是-1
d->iterators = 0;
return DICT_OK;
}
staticvoid _dictReset(dictht *ht)
{
ht->table = NULL; //默认还不分配
ht->size = 0;
ht->sizemask = 0;
ht->used = 0;
}
//创建hash表/rehash
intdictExpand(dict *d, unsigned long size)
{
dictht n; /* the new hash table */
// 根据 size 参数,计算哈希表的大小
unsigned long realsize =_dictNextPower(size);
/* the size is invalid if it is smallerthan the number of
* elements already inside the hash table*/
if (dictIsRehashing(d) || d->ht[0].used> size)
return DICT_ERR;
/* Allocate the new hash table andinitialize all pointers to NULL */
n.size = realsize;
n.sizemask = realsize-1;
// T = O(N)
n.table =zcalloc(realsize*sizeof(dictEntry*));
n.used = 0;
/* Is this the first initialization? If soit's not really a rehashing
* we just set the first hash table so thatit can accept keys. */
if (d->ht[0].table == NULL) {
d->ht[0] = n;
return DICT_OK;
}
/* Prepare a second hash table forincremental rehashing */
d->ht[1] = n;
//扩展完毕后需要设置rehash 为0,下一次就可以进行单步rehash了
d->rehashidx = 0;
return DICT_OK;
}
staticunsigned long _dictNextPower(unsigned long size)
{
unsigned long i = DICT_HT_INITIAL_SIZE; //默认是4
if (size >= LONG_MAX) return LONG_MAX;
while(1) {
if (i >= size)//如果小于4,咱就分配4个
return i;
i *= 2;//直到i大于等于size
}
}
//在字典中添加一个键值对
intdictAdd(dict *d, void *key, void *val)
{
// 尝试添加键到字典,并返回包含了这个键的新哈希节点
// T = O(N)
dictEntry *entry = dictAddRaw(d,key);
// 键已存在,添加失败
if (!entry) return DICT_ERR;
// 键不存在,设置节点的值
// T = O(1)
dictSetVal(d, entry, val);
// 添加成功
return DICT_OK;
}
dictEntry*dictAddRaw(dict *d, void *key) //获取一个包含键的hash表节点
{
int index;
dictEntry *entry;
dictht *ht;
//如果条件允许,进行单步rehash
if (dictIsRehashing(d)) _dictRehashStep(d);
/* Get the index of the new element, or -1if
* the element already exists. */
if ((index =_dictKeyIndex(d, key)) == -1)
return NULL;
// T = O(1)
/* Allocate the memory and store the newentry */
ht = dictIsRehashing(d) ? &d->ht[1]: &d->ht[0]; // 如果字典正在 rehash ,那么将新键添加到 1 号哈希表
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;
}
//步长为1的rehash
staticvoid _dictRehashStep(dict *d) {
if (d->iterators == 0) dictRehash(d,1);
}
//rehash
intdictRehash(dict *d, int n) {
// 只可以在 rehash 进行中时执行
if (!dictIsRehashing(d)) return 0;
while(n--) {
dictEntry *de, *nextde;
/* Check if we already rehashed thewhole table... */
if (d->ht[0].used == 0) {
zfree(d->ht[0].table);//如果rehash完毕
d->ht[0] = d->ht[1];//设置默认hashtable为第一个hashtable
_dictReset(&d->ht[1]);
d->rehashidx = -1;
return 0;
}
/* Note that rehashidx can't overflowas we are sure there are more
* elements because ht[0].used != 0 */
assert(d->ht[0].size >(unsigned)d->rehashidx);
//找到第一个非空hashtable元素
while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
de =d->ht[0].table[d->rehashidx];
/* Move all the keys in this bucketfrom the old to the new hash HT */
while(de) {
unsigned int h;
// 保存下个节点的指针
nextde = de->next;
/* Get the index in the new hashtable */
//重新计算hash值
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;
}
//在字典中查找键为key的节点返回索引
staticint _dictKeyIndex(dict *d, const void *key)
{
unsigned int h, idx, table;
dictEntry *he;
//这里进行扩展
/* Expand the hash tableif needed */
if (_dictExpandIfNeeded(d)== DICT_ERR)
return -1;
/* Compute the key hash value */
h = dictHashKey(d, key);
// T = O(1)
for (table = 0; table <= 1; table++) {
idx = h & d->ht.sizemask;.table[idx];
一、 内存分配
redis内存分配函数是在文件zmalloc.h和zmalloc.c里面进行声明和定义的,主要的函数如下:
void*zmalloc(size_t size);//分配内存
void*zrealloc(void *ptr, size_t size); //重分配内存
voidzfree(void *ptr);//释放内存
redis使用了zmalloc zrealloc zfree来封装了内存管理的函数,这里针对不同的平台来封装,从而屏蔽了底层的差异性实现跨平台,定义如下:
#ifdefined(USE_TCMALLOC)//如果系统存在tcmalloc
#defineZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "."__xstr(TC_VERSION_MINOR))
#include<google/tcmalloc.h>
#if(TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) ||(TC_VERSION_MAJOR > 1)
#defineHAVE_MALLOC_SIZE 1
#definezmalloc_size(p) tc_malloc_size(p)
#else
#error"Newer version of tcmalloc required"
#endif
#elifdefined(USE_JEMALLOC) //如果系统存在jemalloc
#defineZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "."__xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))
#include<jemalloc/jemalloc.h>
#if(JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) ||(JEMALLOC_VERSION_MAJOR > 2)
#defineHAVE_MALLOC_SIZE 1
#definezmalloc_size(p) je_malloc_usable_size(p)
#else
#error"Newer version of jemalloc required"
#endif
#elifdefined(__APPLE__) //如果是苹果平台
#include<malloc/malloc.h>
#defineHAVE_MALLOC_SIZE 1
#definezmalloc_size(p) malloc_size(p)
#endif
#ifndefZMALLOC_LIB
#defineZMALLOC_LIB "libc"
#endif
定义平台之间的差异,主要是tcmalloc(google)、jemalloc(facebook)、苹果平台。
上边说过,封装就是为了屏蔽底层平台的差异,同时方便自己实现相关的统计函数。具体来说就是:
若系统中存在Google的TC_MALLOC库,则使用tc_malloc一族函数代替原本的malloc一族函数。
若当前系统是Mac系统,则使用<malloc/malloc.h>中的内存分配函数。
其他情况,在每一段分配好的空间前头,同时多分配一个定长的字段,用来记录分配的空间大小。
#ifdefHAVE_MALLOC_SIZE
#definePREFIX_SIZE (0)
#else
#ifdefined(__sun) || defined(__sparc) || defined(__sparc__)
#definePREFIX_SIZE (sizeof(long long))
#else
#definePREFIX_SIZE (sizeof(size_t))
#endif
#endif
如果是sun下就使用 sizeof(long long),如果是linux就使用sizeof(size_t)了。
二、 简单字符串
适用场景:
redis默认字符串都是使用sds。
优点:
获取字符串长度:复杂度O(1),直接使用sdshdr->len就能获取到长度。而常规的是o(N)。
二进制安全存储:使用长度来规定了数据的存放长度,而c语言字符数组遇到\0就认为是结尾了,使得redis不仅仅可以存放字符串还可以存放任意二进制数据。
杜绝缓冲区溢出:sds存放的数据长度都是指定的,不存在溢出。
修改字符串减少内存重分配次数:在sdscat调用的sdsMakeRoomFor实现中当拷贝一个比当前存储区大的字符串的时候,如果小于1m会直接分配2倍当前字符串的大小空间,如果大于1m会在当前字符串长度的基础上面多分配1m的空间。
惰性释放:在sdstrim(sdsclear)中,会把删除的字符空间长度累加sdshdr->free里。
原理解释:
简单动态字符串(simpledynamic string,SDS),定义在sds.h头文件里面,包含了sds的定义以及sds相关的基础操作函数。
sds定义:
struct sdshdr {
int len;
int free;
char buf[];
};
这里面最后一个buf[]使用0其实是一种叫做柔性数组的技巧:
redis使用sds和常规的字符串好处有:
sds定义的API有:
sdsnew :创建一个给定c字符串的sds
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init); //取字符串长度
return sdsnewlen(init, initlen);
}
sds sdsnewlen(const void *init, size_tinitlen) {
struct sdshdr *sh;
if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1); //内容 头部+数据
}else {
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
if (sh == NULL) return NULL;
sh->len = initlen;
sh->free = 0;
if (initlen && init)
memcpy(sh->buf, init, initlen);
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}
sdslen :返回sds已经使用的空间字节数
这种通过sds可以直接获取到sds的头部
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
sdscat:追加一个字符串到sds尾部
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
size_t free = sdsavail(s);
size_t len, newlen;
if (free >= addlen) return s;
len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr)));
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC) // SDS_MAX_PREALLOC=1 M大小 ,如果小于1M咱就分配字符串长度的2倍
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC; //如果大于1M,咱就在当前字符串的长度再分配1m的空间
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
if (newsh == NULL) return NULL;
newsh->free = newlen - len;
return newsh->buf;
}
sds sdscatlen(sds s, const void *t, size_tlen) {
struct sdshdr *sh;
size_t curlen = sdslen(s);
s= sdsMakeRoomFor(s,len); //减少重分配
if (s == NULL) return NULL;
sh = (void*) (s-(sizeof(struct sdshdr)));
memcpy(s+curlen, t, len);
sh->len = curlen+len;
sh->free = sh->free-len; //惰性释放
s[curlen+len] = '\0';
return s;
}
sds sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
sdstrim:清除sds首尾的字符(可指定多个)
sds sdstrim(sds s, const char *cset) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
char *start, *end, *sp, *ep;
size_t len;
sp = start = s;
ep = end = s+sdslen(s)-1;
while(sp <= end && strchr(cset, *sp)) sp++;
while(ep > start && strchr(cset, *ep)) ep--;
len = (sp > ep) ? 0 : ((ep-sp)+1);
if (sh->buf != sp) memmove(sh->buf, sp, len);
sh->buf[len] = '\0';
sh->free = sh->free+(sh->len-len); //惰性释放
sh->len = len;
return s;
}
三、 双端链表
适用场景:
链表在redis应用比较广泛,列表键底层实现之一就是链表;客户端信息;
采用双端链表可以定位到头部或者尾部,每个节点的数据类型为任意数据类型,通过定义复制、释放、比较的函数指针实现了可以操作任意用户自定义数据。
原理解释:
链表节点:
typedef struct listNode {
struct listNode *prev; //前驱
struct listNode *next;//后继
void *value; //值指针
} listNode;
链表定义:
typedef struct list {
listNode *head;
listNode *tail;
// 节点值复制函数
void *(*dup)(void *ptr);
// 节点值释放函数
void (*free)(void *ptr);
// 节点值对比函数
int (*match)(void *ptr, void *key);
// 链表所包含的节点数量
unsigned long len;
} list;
/* Prototypes */
list *listCreate(void);
void listRelease(list *list);
list *listAddNodeHead(list *list, void*value);
list *listAddNodeHead(list *list, void*value)
{
listNode *node;
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
node->value = value;
if (list->len == 0) { //如果链表为空
list->head = list->tail = node;
node->prev = node->next = NULL;
}else {
node->prev = NULL;
node->next = list->head;
list->head->prev = node;
list->head = node;
}
list->len++;
return list;
}
四、 字典
适用场景:
在redis中字典用的应用很广泛的,因为redis是一个键值(k-v)内存数据库,所以存储的都是以字典作为基准的,字典也是作为hash键的底层之一:当保存的元素都是比较差的字符串或者hash键值比较多的时候就会使用字典来存储。
原理解释:
由于字典是由hash表存储的,hash表的定义如下:
typedefstruct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表大小
unsigned long sizemask; // 哈希表大小掩码,用于计算索引值,等于 size -1
unsigned long used; // 已经使用的
}dictht;
hash表节点
typedefstruct dictEntry {
void *key;
union { //是一个联合体 用于存放不同的数据类型
void *val;
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next; //主要是用于解决键冲突问题
}dictEntry;
字典类型:
typedefstruct dictType {
// hash函数
unsigned int (*hashFunction)(const void*key);
// 复制键
void *(*keyDup)(void *privdata, const void*key);
// 复制值
void *(*valDup)(void *privdata, const void*obj);
// 对比键
int (*keyCompare)(void *privdata, constvoid *key1, const void *key2);
// 销毁键
void (*keyDestructor)(void *privdata, void*key);
// 销毁值
void (*valDestructor)(void *privdata, void*obj);
}dictType;
字典的定义:
typedefstruct dict {
dictType *type; //可以实现任意用户类型,只需要设定了特定的类型操作函数
void *privdata;
dictht ht[2];
int rehashidx; /* rehashing not in progressif rehashidx == -1 */
int iterators; /* number of iteratorscurrently running */
}dict;
//其中rehash是对hash表进行增大或者减小,当hash表的负载因子(负载因子=dictht.used/dictht.size)
创建一个字典
dict*dictCreate(dictType *type, void *privDataPtr);
dict*dictCreate(dictType *type,
void *privDataPtr)
{
dict *d = zmalloc(sizeof(*d)); //字典分配内存
_dictInit(d,type,privDataPtr);
return d;
}
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; //默认是-1
d->iterators = 0;
return DICT_OK;
}
staticvoid _dictReset(dictht *ht)
{
ht->table = NULL; //默认还不分配
ht->size = 0;
ht->sizemask = 0;
ht->used = 0;
}
//创建hash表/rehash
intdictExpand(dict *d, unsigned long size)
{
dictht n; /* the new hash table */
// 根据 size 参数,计算哈希表的大小
unsigned long realsize =_dictNextPower(size);
/* the size is invalid if it is smallerthan the number of
* elements already inside the hash table*/
if (dictIsRehashing(d) || d->ht[0].used> size)
return DICT_ERR;
/* Allocate the new hash table andinitialize all pointers to NULL */
n.size = realsize;
n.sizemask = realsize-1;
// T = O(N)
n.table =zcalloc(realsize*sizeof(dictEntry*));
n.used = 0;
/* Is this the first initialization? If soit's not really a rehashing
* we just set the first hash table so thatit can accept keys. */
if (d->ht[0].table == NULL) {
d->ht[0] = n;
return DICT_OK;
}
/* Prepare a second hash table forincremental rehashing */
d->ht[1] = n;
//扩展完毕后需要设置rehash 为0,下一次就可以进行单步rehash了
d->rehashidx = 0;
return DICT_OK;
}
staticunsigned long _dictNextPower(unsigned long size)
{
unsigned long i = DICT_HT_INITIAL_SIZE; //默认是4
if (size >= LONG_MAX) return LONG_MAX;
while(1) {
if (i >= size)//如果小于4,咱就分配4个
return i;
i *= 2;//直到i大于等于size
}
}
//在字典中添加一个键值对
intdictAdd(dict *d, void *key, void *val)
{
// 尝试添加键到字典,并返回包含了这个键的新哈希节点
// T = O(N)
dictEntry *entry = dictAddRaw(d,key);
// 键已存在,添加失败
if (!entry) return DICT_ERR;
// 键不存在,设置节点的值
// T = O(1)
dictSetVal(d, entry, val);
// 添加成功
return DICT_OK;
}
dictEntry*dictAddRaw(dict *d, void *key) //获取一个包含键的hash表节点
{
int index;
dictEntry *entry;
dictht *ht;
//如果条件允许,进行单步rehash
if (dictIsRehashing(d)) _dictRehashStep(d);
/* Get the index of the new element, or -1if
* the element already exists. */
if ((index =_dictKeyIndex(d, key)) == -1)
return NULL;
// T = O(1)
/* Allocate the memory and store the newentry */
ht = dictIsRehashing(d) ? &d->ht[1]: &d->ht[0]; // 如果字典正在 rehash ,那么将新键添加到 1 号哈希表
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;
}
//步长为1的rehash
staticvoid _dictRehashStep(dict *d) {
if (d->iterators == 0) dictRehash(d,1);
}
//rehash
intdictRehash(dict *d, int n) {
// 只可以在 rehash 进行中时执行
if (!dictIsRehashing(d)) return 0;
while(n--) {
dictEntry *de, *nextde;
/* Check if we already rehashed thewhole table... */
if (d->ht[0].used == 0) {
zfree(d->ht[0].table);//如果rehash完毕
d->ht[0] = d->ht[1];//设置默认hashtable为第一个hashtable
_dictReset(&d->ht[1]);
d->rehashidx = -1;
return 0;
}
/* Note that rehashidx can't overflowas we are sure there are more
* elements because ht[0].used != 0 */
assert(d->ht[0].size >(unsigned)d->rehashidx);
//找到第一个非空hashtable元素
while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
de =d->ht[0].table[d->rehashidx];
/* Move all the keys in this bucketfrom the old to the new hash HT */
while(de) {
unsigned int h;
// 保存下个节点的指针
nextde = de->next;
/* Get the index in the new hashtable */
//重新计算hash值
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;
}
//在字典中查找键为key的节点返回索引
staticint _dictKeyIndex(dict *d, const void *key)
{
unsigned int h, idx, table;
dictEntry *he;
//这里进行扩展
/* Expand the hash tableif needed */
if (_dictExpandIfNeeded(d)== DICT_ERR)
return -1;
/* Compute the key hash value */
h = dictHashKey(d, key);
// T = O(1)
for (table = 0; table <= 1; table++) {
idx = h & d->ht