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来操作的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值