SDS
定义
从代码中找出原定义
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
typedef char *sds;
在源码里,还有sdshdr5,sdshdr16,sdshdr32这样的其他定义,其中sdshdr5不使用。在代码里直接使用的,都是自定义的sds类型。这里可以看到,我们使用的sds实际上就是一个char*的指针,具体的使用方式如下:
有 sds s;其中s实际指向的是结构体中的buf。flags中存储了是sdshdr16,还是sdshdr32等信息。通过__attribute__ ((packed))可以取消字节对齐,所以可以很容易的根据sds所指向的buf,来找出其他的len,alloc,flags信息。
给sds分配空间
sds sdsMakeRoomFor(sds s, size_t addlen)
当s需要添加长度addlen的数据时,调用这个函数来重新分配空间
当alloc-len > addlen时,无需分配新空间,直接返回。需要分配空间时,当s的大小在 1Mb 以内的时候,分配(len+addlen)*2的大小。当s的大小在 1Mb 以上的时候,分配(len+addlen)+1MB的大小。
可以看出来实际上是多分配了一些空间的。
主要注意的是,分配空间后要注意是否type需要改变,选择对应的sdshdr16或者sdshdr32等等
List
list的节点构造如下
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
对于一个链表list,使用如下数据结构表示
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;
这里的dup和free和match是有三个函数指针,基本就是让list可以匹配多种类型
dict
redis里字典由hash表实现。ps:在C++里map应该是由红黑树实现,unordered_map由hash表生成
哈希表的构造
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
存储键值对的数据结构
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
dictEntry用来指向一个[key,val]的结构体,所以table可以类比为一个数组,dictEntry[n]这样的一个构造。代码中的sizemask = size -1,用来计算key应该放在table中的索引index,假设哈希函数是hash,则计算方式为index=hash(key) & sizemask。这个sizemask可以保证index<size。
从上面可以看出来,index的取值是有限的,所以当两个key的index一致时,就会使用链式法来解决哈希冲突的问题。必要时会扩充table,增加size的值。
扩展的方法
static int dictTypeExpandAllowed(dict *d) {
if (d->type->expandAllowed == NULL) return 1;
return d->type->expandAllowed(
_dictNextPower(d->ht[0].used + 1) * sizeof(dictEntry*),
(double)d->ht[0].used / d->ht[0].size);
}
_dictNextPower(d->ht[0].used + 1)是大于d->ht[0].used + 1的最小的2^n
跳跃表
文件位置位于server.h,这一点是与之前的不同
这是跳跃表的每一个节点
typedef struct zskiplistNode {
sds ele; //保存的字符串
double score; // 相应的分数
struct zskiplistNode *backward; //这个是指向前一个节点的指针
struct zskiplistLevel {
struct zskiplistNode *forward; // 后续的节点
unsigned long span; //当前节点和后续的节点中间隔了多少个跳跃表节点
} level[];
} zskiplistNode;
整数集合
集合键在只有整数,且数量不多的情况下,就会使用整数集
typedef struct intset {
uint32_t encoding;
uint32_t length; //保存元素的个数
int8_t contents[];
} intset;
内容存储在contents中,这里存储多少字节的整数类型,是由encoding决定的。我们用一个find的具体代码来分析
/* Determine whether a value belongs to this set */
uint8_t intsetFind(intset *is, int64_t value) {
uint8_t valenc = _intsetValueEncoding(value); //用来确定是多少字节的数
return valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,NULL);
}
这里的_intsetValueEncoding(value)会根据value的大小,返回编码方式
/* Search for the position of "value". Return 1 when the value was found and
* sets "pos" to the position of the value within the intset. Return 0 when
* the value is not present in the intset and sets "pos" to the position
* where "value" can be inserted. */
static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;
int64_t cur = -1;
while(max >= min) {
mid = ((unsigned int)min + (unsigned int)max) >> 1;
cur = _intsetGet(is,mid);
if (value > cur) {
min = mid+1;
} else if (value < cur) {
max = mid-1;
} else {
break;
}
}
中间省略了一部分
}
这里其实使用的就是二分查找,我们重点看一下是如何获取值的
/* Return the value at pos, using the configured encoding. */
static int64_t _intsetGet(intset *is, int pos) {
return _intsetGetEncoded(is,pos,intrev32ifbe(is->encoding));
}
static int64_t _intsetGetEncoded(intset *is, int pos, uint8_t enc) {
int64_t v64;
int32_t v32;
int16_t v16;
if (enc == INTSET_ENC_INT64) {
memcpy(&v64,((int64_t*)is->contents)+pos,sizeof(v64));
memrev64ifbe(&v64);
return v64;
} else if (enc == INTSET_ENC_INT32) {
memcpy(&v32,((int32_t*)is->contents)+pos,sizeof(v32));
memrev32ifbe(&v32);
return v32;
} else {
memcpy(&v16,((int16_t*)is->contents)+pos,sizeof(v16));
memrev16ifbe(&v16);
return v16;
}
}
其实重点就是这一句memcpy(&v64,((int64_t*)is->contents)+pos,sizeof(v64));
通过((int64_t*)is->contents)+pos找到对应的位置
ziplist
ziplist没有相应的结构体,而是使用了宏来进行操作。
ziplist:
<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
4字节 4字节 2字节 不定 不定 不定 0xFF,一字节
其中zlbytes指ziplist所占的所有字节,zltail用于指向最后一个entry,zllen是entry的个数
entry:
<prevlen> <encoding> <entry-data>
prevlen是前面一个entry所占的字节,大小可以是1字节或者5字节。prevlen<=253时,是1字节,否则为5字节
<prevlen from 0 to 253> <encoding> <entry>
0xFE <4 bytes unsigned little endian prevlen> <encoding> <entry> //后面的4字节用来标识长度
encoding用来标识后面的entry-data是string还是数字。以及保存数据的长度
/* Don't let ziplists grow over 1GB in any case, don't wanna risk overflow in
* zlbytes*/
#define ZIPLIST_MAX_SAFETY_SIZE (1<<30)
int ziplistSafeToAdd(unsigned char* zl, size_t add) {
size_t len = zl? ziplistBlobLen(zl): 0;
if (len + add > ZIPLIST_MAX_SAFETY_SIZE)
return 0;
return 1;
}
从上面的注释可以看到,ziplists不应该用于存储大的内容