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

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

作者:互联网

***********************************************************************************************
/* 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