Redis 设计与实现 10:五大数据类型之有序集合
有序集合 sorted set (下面我们叫zset 吧) 有两种编码方式:压缩列表 ziplist 和跳表 skiplist。
编码一:ziplist
zset 在 ziplist 中,成员(member)和分数(score)是挨在一起的,元素按照分数从小到大存储。
举个例子,我们用以下命令创建一个zset:
redis> ZADD key 26.1 z 1 a 2 b
(integer) 3
那么这个zset的结构大致如下:

下面我们来分析一下 zscore 命令的源码,进一步了解 zset 是如何利用 ziplist 存储的
int zsetScore(robj *zobj, sds member, double *score) {
// ...
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
if (zzlFind(zobj->ptr, member, score) == NULL) return C_ERR;
}
// ...
return C_OK;
}
unsigned char *zzlFind(unsigned char *zl, sds ele, double *score) {
// eptr 是 member 的指针,sptr 是 score 的指针
unsigned char *eptr = ziplistIndex(zl,0), *sptr;
// 遍历 ziplist
while (eptr != NULL) {
// 因为 member 和 score 是挨着存储的,所以获取 member 的下一个节点就是 score 啦
sptr = ziplistNext(zl,eptr);
serverAssert(sptr != NULL);
// 对比当前的 member 和要查询的 member 是否相等
if (ziplistCompare(eptr,(unsigned char*)ele,sdslen(ele))) {
// 如果相等,则获取分数
if (score != NULL) *score = zzlGetScore(sptr);
return eptr;
}
// 不相等则继续往下遍历
eptr = ziplistNext(zl,sptr);
}
return NULL;
}
// 获取分数
double zzlGetScore(unsigned char *sptr) {
unsigned char *vstr;
unsigned int vlen;
long long vlong;
char buf[128];
double score;
serverAssert(sptr != NULL);
// ziplistGet 通过 sptr 指针获取值。根据节点的编码(前文有说到ziplist节点的编码) 对参数赋值
// 如果是字符串,则赋值到 vstr; 如果是整数,则赋值到 vlong。
serverAssert(ziplistGet(sptr,&vstr,&vlen,&vlong));
if (vstr) {
// 如果是字符串,那么存的就是浮点数
memcpy(buf,vstr,vlen);
buf[vlen] = '\0';
// 字符串转换成浮点数
score = strtod(buf,NULL);
} else {
// 整数类型就直接赋值
score = vlong;
}
return score;
}
编码二:skiplist
skiplist 的中文名叫 "跳表"或者"跳跃表"。
下面是跳表的结构图 (图片来自 《Redis 设计与实现》图片集 )

- 图中最左部分就是
zskiplist结构,其代码实现如下(server.h):
typedef struct zskiplist {
// 头指针和尾指针,指向头尾节点
struct zskiplistNode *header, *tail;
// 跳表的节点数(不包含头结点,空跳表也会包含头结点)
unsigned long length;
// 所有节点中,最大的层数
int level;
} zskiplist;
- 图中右边的四个节点,就是跳表节点
zskiplistNode,其代码实现如下(server.h):
typedef struct zskiplistNode {
// 成员
sds ele;
// 分数
double score;
// 后退指针,指向前一个节点
struct zskiplistNode *backward;
// 层,每个节点可能有很多层,每个层可能指向不同的节点
struct zskiplistLevel {
// 前进指针,指向下一个节点
struct zskiplistNode *forward;
// 跟下一个节点之间的跨度
unsigned long span;
} level[];
} zskiplistNode;
跳表最重要的一个地方就是层 level,为什么这么说呢?
假设zset 用链表有序存储,如果我们要查找数据,只能从头到尾遍历,时间复杂度是 \(O(n)\),效率很低。

有什么办法提高效率呢?我们可以在上面添加一层索引。

可以看出,我们遍历的性能变高了。例如我们想找到 6,先遍历第一层,5 到 7 之间,再往下探,就能找到 6 了!
有读者就发现了,如果数据量很大,那找起来也很慢。
是的,那么怎么解决呢?再往上加索引呗!

这不,链表就变成了跳表了!而上面说的层,就是这些索引啦!最终跳表的查找时间复杂度是 \(O(logn)\)
我们来看看 zrange 命令的核心实现,来感受一下跳表的遍历吧
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--) {
// 只要遍历的数没有达到 rank,就一直遍历
while (x->level[i].forward && (traversed + x->level[i].span) <= rank)
{
// 每次加上层的跨度
traversed += x->level[i].span;
// 往前走
x = x->level[i].forward;
}
// 如果这一层走完还没到 rank,那就往下层走,如果还是找不到就继续走,直到走到最底层
if (traversed == rank) {
return x;
}
}
return NULL;
}
Redis 设计与实现 10:五大数据类型之有序集合的更多相关文章
- Redis 源码简洁剖析 04 - Sorted Set 有序集合
Sorted Set 是什么 Sorted Set 命令及实现方法 Sorted Set 数据结构 跳表(skiplist) 跳表节点的结构定义 跳表的定义 跳表节点查询 层数设置 跳表插入节点 zs ...
- redis学习(七)——五大数据类型总结:字符串、散列、列表、集合和有序集合
目录 字符串类型(String) 散列类型(Hash) 列表类型(List) 集合类型(Set) 有序集合类型(SortedSet) 其它命令 一.字符串类型(String) 1.介绍: 字符串类型是 ...
- 探索Redis设计与实现10:Redis的事件驱动模型与命令执行过程
本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...
- Redis学习笔记(七)——数据结构之有序集合(sorted set)
一.介绍 Redis有序集合和集合一样都是string类型元素的机会,且不允许重复的成员. 不同的是每个元素都会关联一个double类型的分数.Redis正是通过分数来为集合中的成员进行从小到放大的排 ...
- Redis入门到高可用(九)——有序集合zset
一.数据结构 集合与有序集合,列表与有序集合的对比 二.主要API zadd 将一个或多个 member 元素及其 score 值加入到有序集 key 当中. zrem 移除有序集 key 中的一个或 ...
- Redis 设计与实现 6:五大数据类型之字符串
前文 Redis 设计与实现 2:Redis 对象 说到,五大数据类型都会封装成 RedisObject. typedef struct redisObject { unsigned type:4; ...
- Redis 详解 (五) redis的五大数据类型实现原理
目录 1.对象的类型与编码 ①.type属性 ②.encoding 属性和 *prt 指针 2.字符串对象 3.列表对象 4.哈希对象 5.集合对象 6.有序集合对象 7.五大数据类型的应用场景 8. ...
- Redis详解(五)------ redis的五大数据类型实现原理
前面两篇博客,第一篇介绍了五大数据类型的基本用法,第二篇介绍了Redis底层的六种数据结构.在Redis中,并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这些对 ...
- redis的五大数据类型实现原理
1.对象的类型与编码 Redis使用前面说的五大数据类型来表示键和值,每次在Redis数据库中创建一个键值对时,至少会创建两个对象,一个是键对象,一个是值对象,而Redis中的每个对象都是由 redi ...
随机推荐
- 基于Fisco-Bcos的区块链智能合约-简单案例实践
一.智能合约介绍 智能合约是指把合同/协议条款以代码的形式电子化地放到区块链网络上.FISCO BCOS平台支持两种智能合约类型:Solidity智能合约与预编译智能合约 Solidity与Java类 ...
- 安卓实用工具箱v4.3几百种小功能
款多功能实用工具箱.提供了从日常.图片.查询.设备.辅助.提取.优惠券.趣味游戏等多方面的功能,操作简单,即点即用,避免您下载超多应用的难题,且应用体积轻巧,界面简洁.已去除广告! 下载地址:http ...
- KVM虚拟机网络
某一天,我的QEMU/KVM虚拟机在打开的时候,出现了以下错误: 查看default配置状态(命令是sudo virsh net-list -all,注意sudo,管理员用户登录的当我没说): 上图是 ...
- Java中字段赋值顺序的问题
static字段 public class Client { public static int i = 2; static { i = 100; } public static void main( ...
- [OI笔记]三种逆元的求法
其实这篇博客只是搬运一下我之前(大概是NOIP那会)写在word里的笔记- 下面直接复制原话,题目是洛谷上求逆元的模板题:https://www.luogu.org/problemnew/show/P ...
- [水题日常]Luogu1113 杂务
这几天又在颓. 我的blog难度目前还比较低,主要面向像我这样子的新手(当然内容也会尽量讲得具体清楚一些)x 如果有错误还请指出~ 写完随笔之后才去翻了一下这题题解之后才注意到这题是有序的 QAQ完全 ...
- WIN7环境下配置vscode c++环境
目录 安装vscode 添加中文环境支持 添加c++支持 配置c++环境 安装MinGW 配置MinGW环境变量 配置vscode launch文件配置 task文件配置 可能出现的问题 安装vsco ...
- 栈的应用:表达式括号匹配检测(C)
问题说明: 假设数学表达式中允许包含两种括号:圆括号"()"和方括号"[]",嵌套顺序任意. 正确的嵌套模式:( [ ] ( ) ).[ ( [ ] [ ] ) ...
- numpy的好处
python是很慢的,因为python在执行代码的时候会执行很多复杂的check功能,比如 b=1; a=b/0.5 这个运算看起来很简单,但是在计算机的内部.b要先从一个整数integer转化成一个 ...
- AD PCB模块复用
该文档为原创,转发需注明出处!https://www.cnblogs.com/brianblog/ 在画图的时候如果遇到PCB中有多个模块原理图是一模一样的时候,我们便会想能不能偷点懒,只画一个模块, ...