ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

redis6.0.5之zset阅读笔记3--跳跃列表(zskiplist)之代码实现2-范围相关函数

2021-02-07 17:34:43  阅读:167  来源: 互联网

标签:zskiplist redis6.0 return zset level min 节点 range spec


***********************************************************************************************
/* Struct to hold a inclusive/exclusive range spec by score comparison. */
通过数值比较 用来保持 闭/开区间 范围确定
typedef struct {
    double min, max;
    int minex, maxex; /* are min or max exclusive? */  最小最大是否是开区间
} zrangespec;

判断传入的值 是否 大于区间最小值
int zslValueGteMin(double value, zrangespec *spec) {
    return spec->minex ? (value > spec->min) : (value >= spec->min);
                          表示开区间             表示闭区间
}

int zslValueLteMax(double value, zrangespec *spec) {
    return spec->maxex ? (value < spec->max) : (value <= spec->max);
}
***********************************************************************************************
/* Returns if there is a part of the zset is in range. */
返回1如果 zset的部分数值在给定区间内,否则返回0
int zslIsInRange(zskiplist *zsl, zrangespec *range) {
    zskiplistNode *x;

    /* Test for ranges that will always be empty. */
    对范围进行测试 ,是否始终为空
    if (range->min > range->max ||  最小值比最大值大,这个集合肯定是空的
            (range->min == range->max && (range->minex || range->maxex))) 最小值和最大值一样大,但是是开区间,也是空集
        return 0;
    x = zsl->tail; 尾巴节点的数值最大
    if (x == NULL || !zslValueGteMin(x->score,range)) 非空但是值比区间最小值小(最大值都够不到区间的最小值).那就不是区间的一部分
        return 0;
    x = zsl->header->level[0].forward; 节点的第一个接地那,数值最小
    if (x == NULL || !zslValueLteMax(x->score,range)) 最小值都比区间的最大值大,那也不是区间的一部分
        return 0;
    return 1;
}
***********************************************************************************************
/* Find the first node that is contained in the specified range.
 * Returns NULL when no element is contained in the range. */
查找第一个数值包含在给定区间范围内的节点。返回空如果没有节点包含在区间范围内
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range) {
    zskiplistNode *x;
    int i;

    /* If everything is out of range, return early. */
    如果不在给定区间内,尽早返回
    if (!zslIsInRange(zsl,range)) return NULL;

    x = zsl->header; 从头结点开始查找
    for (i = zsl->level-1; i >= 0; i--) {
        /* Go forward while *OUT* of range. */ 如果不在给定区间,一直往前
        while (x->level[i].forward && 存在前向节点
            !zslValueGteMin(x->level[i].forward->score,range)) 前向节点小于给定区间最小值
                x = x->level[i].forward; 继续查找下一个节点
    }

    /* This is an inner range, so the next node cannot be NULL. */
    这是一个内部范围(因为上面的函数zslIsInRange确认过了,最后一个节点必定大于给定区间的最小值),
    所以下一个节点肯定不为空,最坏情况也有一个最后节点(但是注意这个节点不一定在给定区间内,可能会大于给定区间最大值)
    x = x->level[0].forward;  
    serverAssert(x != NULL);
(这个函数是求解 两个集合交集的第一个节点的数值, 一个集合是我们的连续给定区间 另外一个是跳表的离散节点数值,
虽然从两个集合的最大最小上看是有交集的,但是因为跳表的是离散的,所以如果节点的值不在交集部分,那就没有真正的区间内节点了
)
给定区间[a, a+20] --------------------------------------|-------------------|--------------------------------------> 
                                                        a                  a+20
跳表中的节点数值  -------a-30-------------a-7-----------|-------------------|------a+24---------------------------->  
上面的情况就没有交点  a+24 这个点不符合要求
    /* Check if score <= max. */ 确认是否小于等于给定区间最大值
    if (!zslValueLteMax(x->score,range)) return NULL; 大于了给定区间的最大值,那么就没有交集,就没有什么第一个了
    return x;
}
***********************************************************************************************
/* Find the last node that is contained in the specified range.
 * Returns NULL when no element is contained in the range. */
查找最后一个数值包含在给定区间范围内的节点。返回空如果没有节点包含在区间范围内
zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec *range) {
    zskiplistNode *x;
    int i;

    /* If everything is out of range, return early. */
    if (!zslIsInRange(zsl,range)) return NULL;

    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        /* Go forward while *IN* range. */
        while (x->level[i].forward &&
            zslValueLteMax(x->level[i].forward->score,range)) 
            前向节点小于给定区间最大值,继续向前(要找到前向大于给定区间最大值,这样当前节点就是符合要求的最大值了,即最后一个)
                x = x->level[i].forward;
    }

    /* This is an inner range, so this node cannot be NULL. */
    serverAssert(x != NULL);

    /* Check if score >= min. */ 检查是否小于最小值
给定区间[a, a+20] --------------------------------------|-------------------|--------------------------------------> 
                                                        a                  a+20
跳表中的节点数值  -------a-30-------------a-7-----------|-------------------|------a+24---------------------------->  
上面的情况就没有交点  a-7 这个点不符合要求

    if (!zslValueGteMin(x->score,range)) return NULL;
    return x;
}
***********************************************************************************************
/* Delete all the elements with score between min and max from the skiplist.
 * Min and max are inclusive, so a score >= min || score <= max is deleted.
 * Note that this function takes the reference to the hash table view of the
 * sorted set, in order to remove the elements from the hash table too. */
删除跳表中所有数值在区间内的节点, 最大最小都包括,
所以一个数值 大于等于最小值 或者(这里应该是笔误,应为并且) 小于等于最大值的节点都需要被删除。
需要注意 这个函数使用 有序集合的hash表视图,也为了从hash表中移除元素
unsigned long zslDeleteRangeByScore(zskiplist *zsl, zrangespec *range, dict *dict) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned long removed = 0;
    int i;

    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        while (x->level[i].forward && (range->minex ?  对每层的节点依次进行比较,找到当前层停止的节点
            x->level[i].forward->score <= range->min : 区间为开区间(端点不包括在内,即端点不符合要求),需要取等于符号
            x->level[i].forward->score < range->min))
                x = x->level[i].forward;
        update[i] = x;
    }

    /* Current node is the last with score < or <= min. */ 当前节点是最后一个小于或者小于等于数值的节点
    x = x->level[0].forward;  下一个节点就是可能需要删除的节点了

    /* Delete nodes while in range. */ 删除区间内的节点
    while (x &&  (range->maxex ? x->score < range->max : x->score <= range->max)) 
    {  这里同样注意区间的开闭,小于或小于等于区间最大值久需要删除
        zskiplistNode *next = x->level[0].forward; 指向跳表下个节点
        zslDeleteNode(zsl,x,update); 删除本节点
        dictDelete(dict,x->ele); 在hash表中也删除
        zslFreeNode(x); /* Here is where x->ele is actually released. */ 这里实际释放节点的字符串
        removed++; 删除节点个数加1
        x = next; 指向下一个节点
    }
    return removed; 返回删除节点个数
}
***********************************************************************************************
比较函数不同,其余皆和zslDeleteRangeByScore类似

/* Struct to hold an inclusive/exclusive range spec by lexicographic comparison. */
typedef struct {
    sds min, max;     /* May be set to shared.(minstring|maxstring) */
    int minex, maxex; /* are min or max exclusive? */
} zlexrangespec;

int zslLexValueGteMin(sds value, zlexrangespec *spec) {
    return spec->minex ?
        (sdscmplex(value,spec->min) > 0) :
        (sdscmplex(value,spec->min) >= 0);
}

int zslLexValueLteMax(sds value, zlexrangespec *spec) {
    return spec->maxex ?
        (sdscmplex(value,spec->max) < 0) :
        (sdscmplex(value,spec->max) <= 0);
}

unsigned long zslDeleteRangeByLex(zskiplist *zsl, zlexrangespec *range, dict *dict) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned long removed = 0;
    int i;

    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        while (x->level[i].forward &&
            !zslLexValueGteMin(x->level[i].forward->ele,range))  小于字符串区间的最小值,继续向前
                x = x->level[i].forward;
        update[i] = x;
    }

    /* Current node is the last with score < or <= min. */
    x = x->level[0].forward;

    /* Delete nodes while in range. */
    while (x && zslLexValueLteMax(x->ele,range)) { 当前节点的字符串小于字符串区间最大值
        zskiplistNode *next = x->level[0].forward;
        zslDeleteNode(zsl,x,update);
        dictDelete(dict,x->ele);
        zslFreeNode(x); /* Here is where x->ele is actually released. */
        removed++;
        x = next;
    }
    return removed;
}
***********************************************************************************************
/* Delete all the elements with rank between start and end from the skiplist.
 * Start and end are inclusive. Note that start and end need to be 1-based */
删除链表中所有位于从开始位置到结束位置的节点。
包括开始和结束.注意开始和结束位置都是基于1的(至少是1,并且都是1的整数倍)
unsigned long zslDeleteRangeByRank(zskiplist *zsl, unsigned int start, unsigned int end, dict *dict) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned long traversed = 0, removed = 0;
    int i;

    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
    对每一层 计算开始删除的节点位置
    还有前向节点,并且 前向节点的位置 还不到 删除开始的位置,继续下一个节点
        while (x->level[i].forward && (traversed + x->level[i].span) < start) {
            traversed += x->level[i].span;
            x = x->level[i].forward;
        }
        update[i] = x; 本层停止查找的位置,即后面的节点已经在删除开始位置之内了
    }

    traversed++; 加1,那么就是开始的第一个删除节点的位置
    x = x->level[0].forward; 指向第一个要删除的节点
    while (x && traversed <= end) {  从第一个删除节点开始 到 结束位置之间的 节点全部删除
        zskiplistNode *next = x->level[0].forward;  
        zslDeleteNode(zsl,x,update);
        dictDelete(dict,x->ele);
        zslFreeNode(x);
        removed++; 移除节点数加1
        traversed++; 位置值加1
        x = next;
    }
    return removed; 返回移除节点数目
}
***********************************************************************************************
/* Find the rank for an element by both score and key.
 * Returns 0 when the element cannot be found, rank otherwise.
 * Note that the rank is 1-based due to the span of zsl->header to the
 * first element. */
通过数值和键查找节点的距离.找不到节点返回0,否则返回距离.
注意距离是基于1(至少是1,并且都是1的整数倍),因为跨度是从链表的头节点到第一个节点之间的举例
unsigned long zslGetRank(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *x;
    unsigned long rank = 0;
    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))) {
            rank += x->level[i].span;
            x = x->level[i].forward;
        }

        /* x might be equal to zsl->header, so test if obj is non-NULL */
        找到的节点可能是链表的头节点,所以测试对象是否为NULL
        
        这部分代码每层的停止节点都会试一试,
        如果恰好该层以下的停止跟该层的节点是同一个,那么就可以直接返回了,即无需垂直向下查找
        if (x->ele && sdscmp(x->ele,ele) == 0) {
            return rank;
        }
    }
    return 0;
}
***********************************************************************************************
/* Finds an element by its rank. The rank argument needs to be 1-based. */
通过距离查找节点,参数距离是基于1的(至少是1,并且都是1的整数倍)
zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) {
    zskiplistNode *x;
    unsigned long traversed = 0;
    int i;

    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        while (x->level[i].forward && (traversed + x->level[i].span) <= rank)
        {
            traversed += x->level[i].span;
            x = x->level[i].forward;
        }
        这里的原理同上一个函数zslGetRank一样,对于垂直向下的情况,不用再多循环了
        if (traversed == rank) { 
            return x;
        }
    }
    return NULL;
}
***********************************************************************************************
/* Struct to hold a inclusive/exclusive range spec by score comparison. */
typedef struct {
    double min, max;
    int minex, maxex; /* are min or max exclusive? */
} zrangespec;

/* Populate the rangespec according to the objects min and max. */
根据对象的最大最小值,填写区间值
static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
    char *eptr;
    spec->minex = spec->maxex = 0;

    /* Parse the min-max interval. If one of the values is prefixed
     * by the "(" character, it's considered "open". For instance
     * ZRANGEBYSCORE zset (1.5 (2.5 will match min < x < max
     * ZRANGEBYSCORE zset 1.5 2.5 will instead match min <= x <= max */
分析传入的大小区间。如果一个值的前缀是字符"(",被认为是开区间。
举例如下: 
整数集合  (1.5 (2.5 对应的是  1.5 < x < 2.5 ,就是开区间
整数集合  1.5 2.5 对应的是  1.5 <= x <= 2.5 ,就是闭区间
    if (min->encoding == OBJ_ENCODING_INT) {
        spec->min = (long)min->ptr;  如果是整数编码,直接赋值就可以了
    } else { 字符串编码,需要转化为数值
        if (((char*)min->ptr)[0] == '(') {
            spec->min = strtod((char*)min->ptr+1,&eptr); 函数strtod将字符串转化为浮点数
            if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
            spec->minex = 1;  这种情况是开区间
        } else {
            spec->min = strtod((char*)min->ptr,&eptr);
            if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
        }
    }
    if (max->encoding == OBJ_ENCODING_INT) {
        spec->max = (long)max->ptr;
    } else {
        if (((char*)max->ptr)[0] == '(') {
            spec->max = strtod((char*)max->ptr+1,&eptr);
            if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
            spec->maxex = 1;开区间
        } else {
            spec->max = strtod((char*)max->ptr,&eptr);
            if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
        }
    }

    return C_OK;
}
***********************************************************************************************

/* ------------------------ Lexicographic ranges ---------------------------- */ 字典序相关的范围
/* Parse max or min argument of ZRANGEBYLEX.
  * (foo means foo (open interval)
  * [foo means foo (closed interval)
  * - means the min string possible
  * + means the max string possible
分析最大或者最小的字典序的参数
(foo 代表 foo是开区间
[foo 代表 foo是闭区间
- 代表最小可能的字符串
+ 代表最小可能的字符串
  * If the string is valid the *dest pointer is set to the redis object
  * that will be used for the comparison, and ex will be set to 0 or 1
  * respectively if the item is exclusive or inclusive. C_OK will be
  * returned.
如果字符串是有效的,那么指针dest就被设置为用来比较的redis对象,
指针ex将被分别设置为0或者1如果项目是排除或者包括,返回C_OK
  * If the string is not a valid range C_ERR is returned, and the value
  * of *dest and *ex is undefined. */
如果字符串是无效的区间,那么返回C_ERR ,指针*dest和*ex的值就没有定义
int zslParseLexRangeItem(robj *item, sds *dest, int *ex) {
    char *c = item->ptr;

    switch(c[0]) {
    case '+':  最大的sds字符串
        if (c[1] != '\0') return C_ERR;
        *ex = 1;
        *dest = shared.maxstring;  
        return C_OK;
    case '-': 最小的sds字符串
        if (c[1] != '\0') return C_ERR;
        *ex = 1;
        *dest = shared.minstring;
        return C_OK;
    case '(': 开区间
        *ex = 1;
        *dest = sdsnewlen(c+1,sdslen(c)-1); 除(外的字符组成sds字符串
        return C_OK;
    case '[':
        *ex = 0;  闭区间
        *dest = sdsnewlen(c+1,sdslen(c)-1);
        return C_OK;
    default:
        return C_ERR;
    }
}
***********************************************************************************************
/* Free a lex range structure, must be called only after zelParseLexRange()
 * populated the structure with success (C_OK returned). */
释放一个字典序的区间结构,必须在函数zelParseLexRange填充区间值成功执行之后调用。
void zslFreeLexRange(zlexrangespec *spec) {
    if (spec->min != shared.minstring &&
        spec->min != shared.maxstring) sdsfree(spec->min); 
    if (spec->max != shared.minstring &&
        spec->max != shared.maxstring) sdsfree(spec->max);
}
***********************************************************************************************
/* Populate the lex rangespec according to the objects min and max.
根据对象最小最大值填写字典序的区间范围。
 * Return C_OK on success. On error C_ERR is returned.
 * When OK is returned the structure must be freed with zslFreeLexRange(),
 * otherwise no release is needed. */
成功返回C_OK,失败返回C_ERR.
返回成功的情况下,这个结构必须被函数zslFreeLexRange释放(否则可能会有内存泄漏问题),失败的情况无需释放
int zslParseLexRange(robj *min, robj *max, zlexrangespec *spec) {
    /* The range can't be valid if objects are integer encoded.
     * Every item must start with ( or [. */
如果传入的是整数编码的,那么区间就无效。每个想必须用字符( 或者 [ 开始。
    if (min->encoding == OBJ_ENCODING_INT ||
        max->encoding == OBJ_ENCODING_INT) return C_ERR;  无效情况直接返回失败

    spec->min = spec->max = NULL;
    if (zslParseLexRangeItem(min, &spec->min, &spec->minex) == C_ERR ||  对最小最大字符串分别解析
        zslParseLexRangeItem(max, &spec->max, &spec->maxex) == C_ERR) {
        zslFreeLexRange(spec); 确定字符串区间
        return C_ERR;
    } else {
        return C_OK;
    }
}
***********************************************************************************************
/* This is just a wrapper to sdscmp() that is able to
 * handle shared.minstring and shared.maxstring as the equivalent of
 * -inf and +inf for strings */
这个函数是函数sdscmp外包了一层,是的能够处理共享的最小最大sds字符相当于 负无穷 和 正无穷
int sdscmplex(sds a, sds b) {
    if (a == b) return 0;  
    if (a == shared.minstring || b == shared.maxstring) return -1;
    if (a == shared.maxstring || b == shared.minstring) return 1;
    return sdscmp(a,b);
}
***********************************************************************************************
用字典序和最小值比较
int zslLexValueGteMin(sds value, zlexrangespec *spec) {
    return spec->minex ?
        (sdscmplex(value,spec->min) > 0) :
        (sdscmplex(value,spec->min) >= 0);
}
用字典序和最大值比较
int zslLexValueLteMax(sds value, zlexrangespec *spec) {
    return spec->maxex ?
        (sdscmplex(value,spec->max) < 0) :
        (sdscmplex(value,spec->max) <= 0);
}
***********************************************************************************************
/* Returns if there is a part of the zset is in the lex range. */
如果集合zset的部分在字典序区间中,就返回1,否则返回0
int zslIsInLexRange(zskiplist *zsl, zlexrangespec *range) {
    zskiplistNode *x;

    /* Test for ranges that will always be empty. */ 确认区间集是否为空
    int cmp = sdscmplex(range->min,range->max);
    if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex))) 
    最小大于最大 或者 等于 但是是开区间  这两种情况区间为空
        return 0;
    x = zsl->tail;
    if (x == NULL || !zslLexValueGteMin(x->ele,range)) 链表最大值 小于 区间最小值, 没有交集
        return 0;
    x = zsl->header->level[0].forward; 
    if (x == NULL || !zslLexValueLteMax(x->ele,range)) 链表最小值 大于 区间最大值, 没有交集
        return 0;
    return 1;
}
***********************************************************************************************
/* Find the first node that is contained in the specified lex range.
 * Returns NULL when no element is contained in the range. */
查找第一个包括在给定字典序范围内的节点。当没有节点包含在给定区间返回空
zskiplistNode *zslFirstInLexRange(zskiplist *zsl, zlexrangespec *range) {
    zskiplistNode *x;
    int i;

    /* If everything is out of range, return early. */ 如果没有交集,尽早返回空
    if (!zslIsInLexRange(zsl,range)) return NULL;

    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        /* Go forward while *OUT* of range. */
        while (x->level[i].forward &&   
            !zslLexValueGteMin(x->level[i].forward->ele,range))
            有前向节点 并且 前向节点的元素 小于区间最小值, 继续下一个节点
                x = x->level[i].forward;
    }

    /* This is an inner range, so the next node cannot be NULL. */
    上面的循环确认了存在下一个节点,所以不为空
    x = x->level[0].forward;
    serverAssert(x != NULL); 虽然理论上不为空,还是进行了检查,防止万一程序有bug

    /* Check if score <= max. */ 同上面的zslFirstInRange一样,需要检查是否超过了最大值
    if (!zslLexValueLteMax(x->ele,range)) return NULL;
    return x;
}
***********************************************************************************************
/* Find the last node that is contained in the specified range.
 * Returns NULL when no element is contained in the range. */
查找最后一个包括在给定字典序范围内的节点。当没有节点包含在给定区间返回空
zskiplistNode *zslLastInLexRange(zskiplist *zsl, zlexrangespec *range) {
    zskiplistNode *x;
    int i;

    /* If everything is out of range, return early. */
    if (!zslIsInLexRange(zsl,range)) return NULL;

    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        /* Go forward while *IN* range. */
        while (x->level[i].forward &&
            zslLexValueLteMax(x->level[i].forward->ele,range))
            有前向节点 并且 前向节点的元素 小于 区间最大值, 继续下一个节点
                x = x->level[i].forward;
    }

    /* This is an inner range, so this node cannot be NULL. */
    serverAssert(x != NULL);

    /* Check if score >= min. */   同上面的zslFirstInRange一样,需要检查是否比最小值小
    if (!zslLexValueGteMin(x->ele,range)) return NULL;
    return x;
}
***********************************************************************************************

 

标签:zskiplist,redis6.0,return,zset,level,min,节点,range,spec
来源: https://www.cnblogs.com/cquccy/p/14385995.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有