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