redis6.0.5之zset阅读笔记3--跳跃列表(zskiplist)之代码实现1-增删改

***********************************************************************************************
typedef struct zskiplistNode {
    sds ele;       元素
    double score;  分值
    struct zskiplistNode *backward;  后向指针,用于层次1
    struct zskiplistLevel {  每层
        struct zskiplistNode *forward;  向前的指针
        unsigned long span;  跳跃的间隔
    } level[]; 
} zskiplistNode;

/* Create a skiplist node with the specified number of levels.
 * The SDS string 'ele' is referenced by the node after the call. */
创建一个带有确定层级的跳表节点,通过调用,SDS字符串'ele'被节点引用
zskiplistNode *zslCreateNode(int level, double score, sds ele) {
    zskiplistNode *zn =
        zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));  节点本身的空间 和 层数 * 每层所需空间
    zn->score = score; 节点的数值
    zn->ele = ele; 节点的元素
    return zn;
}
***********************************************************************************************
typedef struct zskiplist {
    struct zskiplistNode *header, *tail;  头和尾指针
    unsigned long length;  跳表长度
    int level;  所在层次
} zskiplist;

/* Create a new skiplist. */
zskiplist *zslCreate(void) {
    int j;
    zskiplist *zsl;

    zsl = zmalloc(sizeof(*zsl));  创建跳表所需初始空间
    zsl->level = 1;  初始只有1层
    zsl->length = 0; 长度为0
    zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);  创建头节点,层级最高为32,数值为0,不带元素
    //#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64 elements */
    for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {  初始化每层的指针 和 跳跃间隔
        zsl->header->level[j].forward = NULL;
        zsl->header->level[j].span = 0;
    }
    zsl->header->backward = NULL; 反向指针
    zsl->tail = NULL; 
    return zsl;
}
***********************************************************************************************
/* Free the specified skiplist node. The referenced SDS string representation
 * of the element is freed too, unless node->ele is set to NULL before calling
 * this function. */
释放指定的跳表节点。元素引用的SDS字符串也需要释放,除非节点的元素在调用本函数前已经被置为NULL
void zslFreeNode(zskiplistNode *node) {
    sdsfree(node->ele);
    zfree(node);
}
***********************************************************************************************
/* Free a whole skiplist. */ 释放整个跳表(先释放跳表内部申请的节点空间,再释放跳表本身)
void zslFree(zskiplist *zsl) {
    zskiplistNode *node = zsl->header->level[0].forward, *next;

    zfree(zsl->header); 释放头节点
    while(node) { 节点不为空
        next = node->level[0].forward;  通过第一层的指向关系获取下一个节点(因为任何节点都有第一层即level[0])
        zslFreeNode(node); 把当前节点释放掉
        node = next;  将下个节点置成当前节点
    }
    zfree(zsl); 释放跳表本身
}
***********************************************************************************************
/* Returns a random level for the new skiplist node we are going to create.
 * The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL
 * (both inclusive), with a powerlaw-alike distribution where higher
 * levels are less likely to be returned. */
为将要新建的跳表节点返回一个随机的层次。
本函数返回的值介于1到ZSKIPLIST_MAXLEVEL之间(包括头和尾),即[1,ZSKIPLIST_MAXLEVEL]
拥有一个类似幂律分布,较高层级不太可能被返回(层数越高返回的可能性越小)
int zslRandomLevel(void) {
    int level = 1;
    //#define ZSKIPLIST_P 0.25      /* Skiplist P = 1/4 */
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))  当随机值小0.25的时候,层级就上升一层
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;  
    如果层级没有超过定义的最高层,就返回本身,否则返回最高层的数值
}
***********************************************************************************************
/* Insert a new node in the skiplist. Assumes the element does not already
 * exist (up to the caller to enforce that). The skiplist takes ownership
 * of the passed SDS string 'ele'. */
在跳跃链表插入一个新节点。假设这个元素不存在(直到调用者执行)。
跳表获得传入SDS字符串ele的拥有权。
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned int rank[ZSKIPLIST_MAXLEVEL];  保留跳跃的步数
    int i, level;

    serverAssert(!isnan(score));  确认传入的数值是合理数值
    x = zsl->header;     指向头部
    for (i = zsl->level-1; i >= 0; i--) {  从链表最高层开始往下确定最终插入位置,同时记录每一层的停止的位置
        /* store rank that is crossed to reach the insert position */  
        保存插入位置每个层级的向前跳跃的步数(即到达同层下个节点的步数)
        rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];  初始化,最高层初始值为0,下层初始值为上层的结束值
        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. */
我们假设元素不在跳表内部,因为我们允许重复的数值,重复插入同样的元素不应该发生,
所以函数zslInsert的调用者需要在哈希表中测试元素是否已经在内部或者没有

    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; 将原节点前面的节点 赋值给 新节点前面的节点
        update[i]->level[i].forward = x; 将原节点前面的节点 变成 新节点

        /* update span covered by update[i] as x is inserted here */ 
        当新节点x插入时,更新对应层级的跳跃步数
        x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
新节点x层级i对应的跳步   = 原节点层级i对应的跳步 - (新节点插入的最终位置-i层原节点所在位置)
如下图所示

--------停止位置------------------------------------------------------------------------------rank[level-1]
...
----------------------------停止位置-------------------------|----------原指向的节点位置------rank[i]
...                             |<----rank[0] - rank[i]----->|                  |
--------------------------------|---------------------停止位置(最终位置)--------|-------------rank[0]
                                |
--------------------------------|<----------原节点层级i对应的跳步-------------->|----------------

那么rank[0] - rank[i] 代表 最终位置  和  对应i层 之间的距离
update[i]->level[i].span - (rank[0] - rank[i]) 就代表新节点到原节点指向的前节点的距离

        update[i]->level[i].span = (rank[0] - rank[i]) + 1; 加1就是要把新节点也计算在内
    }

    /* increment span for untouched levels */ 
    新增节点没有使用的层,原来所在位置的节点的跳跃步数都需要增加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++; 跳表总长度增加1
    return x; 返回这个新增节点
}
***********************************************************************************************
/* Internal function used by zslDelete, zslDeleteRangeByScore and
 * zslDeleteRangeByRank. */
被函数zslDelete, zslDeleteRangeByScore 和 zslDeleteRangeByRank使用的内部函数, 用来删除节点
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
    int i;
    for (i = 0; i < zsl->level; i++) {
        if (update[i]->level[i].forward == x) {  如果当前节点的前向节点是x,x就是将要被删除的节点
            update[i]->level[i].span += x->level[i].span - 1;  
            那么这个节点的跳跃步数 需要加上其前向节点(就是x)的跳跃步数,同时减去这个删除节点
            update[i]->level[i].forward = x->level[i].forward; 把删除节点的前向节点赋值给 当前节点的前向节点
        } else {
            update[i]->level[i].span -= 1;不是前向节点,那么对应的跳跃步数减去1即可
        }
    }
    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--; 链表总元素个数减少1
}
***********************************************************************************************
/* Delete an element with matching score/element from the skiplist.
 * The function returns 1 if the node was found and deleted, otherwise
 * 0 is returned.
从跳表中删除匹配数值或者元素的节点。节点被找到和删除就返回1,否则返回0
 * If 'node' is NULL the deleted node is freed by zslFreeNode(), otherwise
 * it is not freed (but just unlinked) and *node is set to the node pointer,
 * so that it is possible for the caller to reuse the node (including the
 * referenced SDS string at node->ele). */
如果传入是空,通过函数zslFreeNode释放删除节点, 否则不释放(只是不链在表中),设置*node指向这个删除节点。
因此调用者重新使用这个节点变得可能(包括在节点node->ele中引用的SDS字符串)

int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    int i;

    x = zsl->header; 向头部节点
    for (i = zsl->level-1; i >= 0; 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)))
        {
            x = x->level[i].forward;
        }
        update[i] = x; 记录本层停止的位置
    }
    /* We may have multiple elements with the same score, what we need
     * is to find the element with both the right score and object. */
我们拥有很多同样数值的元素,我们需要找出那个具有同样数值和对象内容的元素.(数值和字符串才构成唯一)
    x = x->level[0].forward; 第一层停止的位置,就是呀删除的位置(如果存在)
    if (x && score == x->score && sdscmp(x->ele,ele) == 0) { 
        zslDeleteNode(zsl, x, update);
        if (!node) 传入空,释放这个节点
            zslFreeNode(x);
        else
            *node = x;  非空的情况下,通过指针node返回删除节点
        return 1;
    }
    return 0; /* not found */
}
***********************************************************************************************
/* Update the score of an elmenent inside the sorted set skiplist.
 * Note that the element must exist and must match 'score'.
 * This function does not update the score in the hash table side, the
 * caller should take care of it.
更新一个在排序结合跳表中的元素的数值。注意这个元素必须存在而且必须匹配这个数值。
这个函数不更新hash表中的数值,调用者需要注意这点。
 * Note that this function attempts to just update the node, in case after
 * the score update, the node would be exactly at the same position.
 * Otherwise the skiplist is modified by removing and re-adding a new
 * element, which is more costly.
注意如果经过数值调整,这个节点可能还是在原来的位置,那么这个函数仅仅只需要更新节点,
否则需要通过重新移除和重新添加一个新节点,这种情况代价比较高。
 * The function returns the updated element skiplist node pointer. */
本函数返回指向更新后的跳表中的节点

zskiplistNode *zslUpdateScore(zskiplist *zsl, double curscore, sds ele, double newscore) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    int i;

    /* We need to seek to element to update to start: this is useful anyway,
     * we'll have to update or remove it. */
我们需要寻找更新元素的开始: 这个很有用,我们必须更新或删除它
( 这个部分同插入和删除的前奏一样,确定层的停止位置。确实很很有用!)
    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        while (x->level[i].forward &&
                (x->level[i].forward->score < curscore ||
                    (x->level[i].forward->score == curscore &&
                     sdscmp(x->level[i].forward->ele,ele) < 0)))
        {
            x = x->level[i].forward;
        }
        update[i] = x;
    }

    /* Jump to our element: note that this function assumes that the
     * element with the matching score exists. */
调到我们需要处理的元素:注意这个函数假设匹配数值的元素存在
    x = x->level[0].forward;
    serverAssert(x && curscore == x->score && sdscmp(x->ele,ele) == 0); 假设了存在,所以检测下,万一不存在就是程序出问题了

    /* If the node, after the score update, would be still exactly
     * at the same position, we can just update the score without
     * actually removing and re-inserting the element in the skiplist. */
如果节点经过数值更新,还是保持原位,我们值需要更新数值即可,不需要将元素移除和重新插入跳表。
    if ((x->backward == NULL || x->backward->score < newscore) && 本节点前一个节点的数值 小于 本节点新的数值 
        (x->level[0].forward == NULL || x->level[0].forward->score > newscore)) 本节点后一个节点的数值 大于 本节点新的数值 
    {
        x->score = newscore;  只需要更新数值即可
        return x;
    }

    /* No way to reuse the old node: we need to remove and insert a new
     * one at a different place. */
不能使用老节点:我们需要移除(老节点)和在不同的地方插入一个新节点
    zslDeleteNode(zsl, x, update); 将老节点删除
    zskiplistNode *newnode = zslInsert(zsl,newscore,x->ele);  插入新节点
    /* We reused the old node x->ele SDS string, free the node now
     * since zslInsert created a new one. */
我们重用了老节点的sds字符串,因为函数zslInsert创建了一个新节点,所以我们可以释放老节点了。(sds字符串有新节点管了,不会有内存泄漏)
    x->ele = NULL;
    zslFreeNode(x);
    return newnode;
}
***********************************************************************************************
posted on 2021-02-05 16:05  子虚乌有  阅读(217)  评论(0)    收藏  举报