redis的数据结构
动态字符串
动态字符串的存储结构:
上面只是最普通的一种情况,对于不同的字符串长度redis中也设计了不同的数据结构来存储。
对于第一种sdshdr的存储结构:
第三位表示数据结构的类型,用来区分结构体的类型,高五位用来表示字符串的长度,所以是sdshdr能表示长度0-31的字符串,sdshdr16的内存方式:
这里__attribute__ ((packed))的作用是用来1字节的内存对齐,所以才会存在上述结构,否则由于内存对齐会多用三字节,这里属于节约内存的做法。
创建字符串:
释放字符串:
s指向字符指针的首部,通过sdsHdrSize()偏移到sdshdr的首部,然后free掉内存。
拼接字符串:
流程:
具体实现可以参考:redis设计与实现50页
跳跃表
跳跃表的模型:
跳跃表的性质:
数据结构:
插入操作:
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
//插入节点时
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
//rank[i]表示从第i层的header节点到update[i]节点所经历的步长,在更新update[i]的span和设置新插入节点的span和设置新插入节点的span时用到
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;
serverAssert(!isnan(score));
x = zsl->header;
//从最高层开始遍历查找要插入的位置
for (i = zsl->level-1; i >= 0; i--) {
//i为最高层时初始化为0,否则初始化为上层的rank值,因为在第i层查找时前面的步长也要加起来
rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
//在第i层查找,直到找到最后一个元素或者找到元素插入的位置
while (x->level[i].forward &&
(x->level[i].forward->score < score ||
(x->level[i].forward->score == score &&
sdscmp(x->level[i].forward->ele,ele) < 0)))
{
//记录步长
rank[i] += x->level[i].span;
//移到该层的下一个节点
x = x->level[i].forward;
}
//记录该层的最后一个节点或者该节点后将移动到下一层的节点
update[i] = x;
}
/* we assume the element is not already inside, since we allow duplicated
* scores, reinserting the same element should never happen since the
* caller of zslInsert() should test in the hash table if the element is
* already inside or not. */
//随机生成插入到第几层
level = zslRandomLevel();
if (level > zsl->level) {
//初始化高出现有层的步长
for (i = zsl->level; i < level; i++) {
rank[i] = 0;
update[i] = zsl->header;
update[i]->level[i].span = zsl->length;
}
//设置跳表的层数
zsl->level = level;
}
x = zslCreateNode(level,score,ele);
//
for (i = 0; i < level; i++) {
//设置新节点在每层的前向节点
x->level[i].forward = update[i]->level[i].forward;
//设置每层更新节点的前向节点为x
update[i]->level[i].forward = x;
//设置步长
x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
update[i]->level[i].span = (rank[0] - rank[i]) + 1;
}
//插入层上面的层的步长要加1
for (i = level; i < zsl->level; i++) {
update[i]->level[i].span++;
}
//设置后向指针的指向
x->backward = (update[0] == zsl->header) ? NULL : update[0];
if (x->level[0].forward)
//如果在尾部后向指针指向自己
x->level[0].forward->backward = x;
else
zsl->tail = x;
//跳跃表的长度增加
zsl->length++;
return x;
}
删除操作:
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
int i;
for (i = 0; i < zsl->level; i++) {
//当update[i]指定的节点的下一个节点是要删除的节点
if (update[i]->level[i].forward == x) {
//设置步长
update[i]->level[i].span += x->level[i].span - 1;
update[i]->level[i].forward = x->level[i].forward;
}
//要删除节点的上层
else {
update[i]->level[i].span -= 1;
}
}
//如果该节点不是最后一个节点,设置backward指针。
if (x->level[0].forward) {
x->level[0].forward->backward = x->backward;
}
else {
zsl->tail = x->backward;
}
//如果删除的最高层的节点就减少高度
while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
zsl->level--;
zsl->length--;
}
压缩列表:
压缩列表的基本结构:
插入元素:
//插入
unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
//curlen当前压缩列表的长度,reqlen新插入元素需要多少字节,newlen元素插入后压缩列表的长度
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, newlen;
//prevlensize表示插入位置p后面的一个元素的previous_entry_length字段占多少字节,prevlen表示插入位置p前一个元素的长度
unsigned int prevlensize, prevlen = 0;
size_t offset;
int nextdiff = 0;
unsigned char encoding = 0;
long long value = 123456789; /* initialized to avoid warning. Using a value
that is easy to see if for some reason
we use it uninitialized. */
zlentry tail;
if (p[0] != ZIP_END) {
//插入位置不在压缩列表的尾端
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
} else {
unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
if (ptail[0] != ZIP_END) {
prevlen = zipRawEntryLengthSafe(zl, curlen, ptail);
}
}
//尝试解析即将插入的字符串s,判断s是不是整数
if (zipTryEncoding(s,slen,&value,&encoding)) {
//如果是整数就计算存储这个整数需要多少字节
reqlen = zipIntSize(encoding);
}
else {
//如果是字符串就直接加上字符串的长度
reqlen = slen;
}
//计算previous_entry_length需要多少字节
reqlen += zipStorePrevEntryLength(NULL,prevlen);
//计算encoding需要多少字节
reqlen += zipStoreEntryEncoding(NULL,encoding,slen);
//判断是否出现了连锁更新的情况
int forcelarge = 0;
//判断后面的元素previous_entry_length是否需要改变
nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
if (nextdiff == -4 && reqlen < 4) {
nextdiff = 0;
forcelarge = 1;
}
//插入点相对于压缩列表的首部的偏移量
offset = p-zl;
//压缩列表新的长度
newlen = curlen+reqlen+nextdiff;
//重新分配内存和设置压缩列表的长度和尾部
zl = ziplistResize(zl,newlen);
//重新定位到插入点的位置
p = zl+offset;
/* Apply memory move when necessary and update tail offset. */
if (p[0] != ZIP_END) {
//拷贝原内容
memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);
/* Encode this entry's raw length in the next entry. */
if (forcelarge)
zipStorePrevEntryLengthLarge(p+reqlen,reqlen);
else
zipStorePrevEntryLength(p+reqlen,reqlen);
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);
/* When the tail contains more than one entry, we need to take
* "nextdiff" in account as well. Otherwise, a change in the
* size of prevlen doesn't have an effect on the *tail* offset. */
//tail的偏移需要把nextdiff也算进去
assert(zipEntrySafe(zl, newlen, p+reqlen, &tail, 1));
if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {
ZIPLIST_TAIL_OFFSET(zl) =
intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
}
} else {
/* This element will be the new tail. */
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
}
//检查连锁更新
if (nextdiff != 0) {
offset = p-zl;
zl = __ziplistCascadeUpdate(zl,p+reqlen);
p = zl+offset;
}
//写入内容
p += zipStorePrevEntryLength(p,prevlen);
p += zipStoreEntryEncoding(p,encoding,slen);
if (ZIP_IS_STR(encoding)) {
memcpy(p,s,slen);
} else {
zipSaveInteger(p,value,encoding);
}
ZIPLIST_INCR_LENGTH(zl,1);
return zl;
}
删除元素:
/* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */
unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {
unsigned int i, totlen, deleted = 0;
size_t offset;
int nextdiff = 0;
zlentry first, tail;
//压缩列表的长度
size_t zlbytes = intrev32ifbe(ZIPLIST_BYTES(zl));
//解码第一个要删除的元素
zipEntry(p, &first);
for (i = 0; p[0] != ZIP_END && i < num; i++) {
p += zipRawEntryLengthSafe(zl, zlbytes, p);
deleted++;
}
assert(p >= first.p);
//要删除元素的总长度
totlen = p-first.p; /* Bytes taken by the element(s) to delete. */
if (totlen > 0) {
uint32_t set_tail;
if (p[0] != ZIP_END) {
//要删除的最后一个元素的下一个元素的previous_entry_length的长度变化
nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);
//更新要删除的最后一个元素的下一个元素的previous_entry_length的值
p -= nextdiff;
assert(p >= first.p && p<zl+zlbytes-1);
zipStorePrevEntryLength(p,first.prevrawlen);
//更新ztail
set_tail = intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-totlen;
//加上那该nextdiff的值
assert(zipEntrySafe(zl, zlbytes, p, &tail, 1));
if (p[tail.headersize+tail.len] != ZIP_END) {
set_tail = set_tail + nextdiff;
}
//拷贝数据
size_t bytes_to_move = zlbytes-(p-zl)-1;
memmove(first.p,p,bytes_to_move);
} else {
//如果一直删除到最后一个元素,ztail就指向要删除元素的前一个元素
set_tail = (first.p-zl)-first.prevrawlen;
}
//重新分配空间
offset = first.p-zl;
zlbytes -= totlen - nextdiff;
zl = ziplistResize(zl, zlbytes);
p = zl+offset;
//重新设置元素个数
ZIPLIST_INCR_LENGTH(zl,-deleted);
//设置ztail
assert(set_tail <= zlbytes - ZIPLIST_END_SIZE);
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(set_tail);
//检查是否会出现级联删除更新的情况
if (nextdiff != 0)
zl = __ziplistCascadeUpdate(zl,p);
}
return zl;
}
字典
字典的模型图:
结构体:
其实如果懂得上面的字典的映射方式,就很容易理解字典的其他一些操作,c++中stl中的hash也是这种结构。
具体参考:redis设计与源码分析130-150
整数集合:
整数集合的结构:
查找元素的方法:
添加元素的方法:
扩容的步骤:
删除元素的步骤:
具体的源码解析参考redis设计与源码分析160-180,这个数据结构还挺简单的,就不贴代码了。
qicklist快速列表
快速列表的增删改查主要是基于链表和ziplist来操作的。