Redis实现之对象(三)

集合对象

集合对象的编码可以是intset或者hashtable,intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面。举个栗子,以下代码将创建一个图1-12所示的intset编码集合对象:

127.0.0.1:6379> SADD numbers 1 3 5
(integer) 3
127.0.0.1:6379> OBJECT ENCODING numbers
"intset"

    

图1-12   inset编码的numbers集合对象

 

另一方面,hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为NULL,以下的示例,将创建一个如图1-13所示的hashtable编码集合对象: 

127.0.0.1:6379> SADD fruits "apple" "banana" "cherry"
(integer) 3
127.0.0.1:6379> OBJECT ENCODING fruits
"hashtable"

  

图1-13   hashtable编码的fruits集合对象

编码的转换

当集合对象可以同时满足以下两个条件时,对象使用intset编码:

  • 集合对象保存的所有元素都是整数值
  • 集合对象保存的元素数量不超过512个

不能满足以上两个条件对的集合对象需要使用hashtable编码,注意,第一个条件是无法修改的,但第二个条件的上限值可以修改,具体请看配置文件中关于set-max-intset-entries选项的说明

对于使用intset编码的集合对象来说,当使用intset编码所需的两个条件的任意一个不能被满足时,就会执行对象的编码转换操作,原本保存在整数集合中的所有元素都会被转移并保存到字典里面,并且对象的编码也会从intset变为hashtable

举个栗子,以下代码创建一个只包含整数元素的集合对象,该对象原来的编码为intset,但我们只要添加一个字符串元素,集合对象的编码转移操作就会被执行

127.0.0.1:6379> SADD numbers 1 3 5
(integer) 3
127.0.0.1:6379> OBJECT ENCODING numbers
"intset"
127.0.0.1:6379> SADD numbers "seven"
(integer) 1
127.0.0.1:6379> OBJECT ENCODING numbers
"hashtable"

  

除此之外,如果我们创建一个包含512个整数元素的集合对象,那么对象的编码应该是intset。但是,只要我们再往集合添加一个整数元素,使得这个集合的元素变为513,那么对象的编码转换操作就会被执行:

127.0.0.1:6379> EVAL "for i=1, 512 do redis.call('SADD', KEYS[1], i) end" 1 integers
(nil)
127.0.0.1:6379> SCARD integers
(integer) 512
127.0.0.1:6379> OBJECT ENCODING integers
"intset"
127.0.0.1:6379> SADD integers 10086
(integer) 1
127.0.0.1:6379> SCARD integers
(integer) 513
127.0.0.1:6379> OBJECT ENCODING integers
"hashtable"

  

集合命令的实现

因为集合键的值为集合对象,所以用于集合键的所有命令都是针对集合对象来操作的,表1-10列出了其中一部分集合键的命令,以及这些命令在不同编码的集合对象下的实现方法

表8-10集合命令的实现方法
命令intset编码的实现方法hashtable编码的实现方法
SADD调用intsetAdd函数,将所有新元素添加到整数集合里面调用dictAdd,以新元素为键,NULL为值,将键值对添加到字典里面
SCARD调用intsetLen函数,返回整数集合所包含的元素数量,这个数量就是集合对象所包含的元素数量调用dictSize函数,返回字典所包含的键值对数量,这个数量就是集合对象所包含的元素数量
SISMEMBER调用intsetFind函数,在整数集合中查找给定的元素,如果找到了说明元素存在于集合,没找到则说明元素不存在于集合调用dictFind 函数,在字典的键中查找给定的元素,如果找到了说明元素存在于集合,没找到则说明元素不存在于集合
SMEMBERS遍历整个整数集合,使用intsetGet函数返回集合元素遍历整个字典,使用dictGetKey函数返回字典的键作为集合元素
SRANDMEMBER调用intsetRandom函数,从整数集合中随机返回一个元素调用dictGetRandomKey函数,从字典中随机返回一个字典键
SPOP调用intsetRandom函数,从整数集合中随机取出一个元素,在将这个随机元素返回给客户端之后,调用intsetRemove函数, 将随机元素从整数集合中删除掉调用dictGetRandomKey函数,从字典中随机取出一个字典键,在将这个随机字典键的值返回给客户端之后,调用 dictDelete函数,从字典中删除随机字典键所对应的键值对
SREM调用intsetRemove函数,从整数集合中删除所有给定的元素调用dictDelete函数,从字典中删除所有键为给定元素的键值对

 

有序集合对象

有序集合的编码可以是ziplist或者skiplist,ziplist编码的压缩列表中,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),而第二个元素保存元素的分值(score)。压缩列表内的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的方向,而分值较大的元素则被放置在靠近表尾的方向

举个栗子,如果我们执行以下ZADD命令,那么服务器将创建一个有序集合对象作为price键的值:

127.0.0.1:6379> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3
127.0.0.1:6379> OBJECT ENCODING price
"ziplist"

  

price这个值对象如图1-14所示,而对象所使用的的压缩列表如图1-15所示

图1-14   ziplist编码的有序集合对象

图1-15   有序集合元素在压缩列表中按分值从小到大排列

skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表

redis.h

typedef struct zset {dict *dict;zskiplist *zsl;
} zset;typedef struct zskiplistNode {robj *obj;double score;struct zskiplistNode *backward;struct zskiplistLevel {struct zskiplistNode *forward;unsigned int span;} level[];
} zskiplistNode;typedef struct zskiplist {struct zskiplistNode *header, *tail;unsigned long length;int level;
} zskiplist;

  

zset结构中的的zsl跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的obj属性保存了元素的成员,而跳跃表节点的score属性则保存了元素的分值。通过这个跳跃表,程序可以对有序集合进行范围型操作,比如ZRANK、ZRANGE等命令就是基于跳跃表API来实现的

除此之外,zset结构中的dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值则保存了元素的分值。通过这个字典,程序可以在O(1)的时间复杂度内查找给定成员的分值,ZSCORE命令就是根据这一特性实现的,而很多其他有序集合命令都在实现的内部用到了这一特性

有序集合中每个元素的成员都是一个字符串对象,而每个元素的分值都是一个double类型的浮点数。值得一提的是,虽然zset结构同时使用跳跃表和字典来保存有序集合元素,但这两种数据结构都会通过指针来共享相同元素的成员和分值,所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或分值,也不会因为浪费额外的内存

为什么有序集合需要同时使用跳跃表和字典来实现?在理论上,有序集合可以单独使用字典或者跳跃表其中一种数据结构来实现,但无论使用字典还是跳跃表,在性能上比起同时使用字典和跳跃表都会有所降低。举个例子,如果我们只是用字典来实现有序集合,那么虽然可以在O(1)的时间复杂度内查找成员对应的分值,但是,因为字典以无序的方式来保存集合元素,所以每次在执行范围型操作——比如:ZRANK、ZRANGE等命令时,程序都需要对字典的所有元素进行排序,完成这种排序至少需要O(N logN)的时间复杂度,以及额外的O(N)内存空间(因为要创建一个数组来保存排序后的元素)

另一方面如果我们只使用跳跃表来实现有序集合,那么跳跃表执行范围型操作时的所有优点都会被保留,但因为没有了字典,所以根据成员查找分值这一操作的时间复杂度将从O(1)上升至O(logN)。因为以上原因,为了让有序集合的查找和范围型操作都尽可能快地执行,Redis选择了同时使用字典和跳跃表两种数据结构来实现有序集合

举个栗子,如果前面的price键创建的不是ziplist编码的有序集合对象,而是skiplist编码的有序集合对象,那么这个有序集合对象将会是图1-16所示的样子,而对象所使用的zset结构将会是图8-17所示的样子

图1-16   skiplist编码的有序集合对象

 

图1-17   有序集合元素同时被保存在字典和跳跃表中

编码的转换

当有序集合对象可以同时满足以下条件时,对象使用ziplist编码:

  • 有序集合保存的元素数量小于128个
  • 有序集合保存的所有元素的长度小于64字节

不能满足以上两个条件的有序集合对象将使用skiplist编码

# 对象包含了 128 个元素
127.0.0.1:6379> EVAL "for i=1, 128 do redis.call('ZADD', KEYS[1], i, i) end" 1 numbers
(nil)
127.0.0.1:6379> ZCARD numbers
(integer) 128
127.0.0.1:6379> OBJECT ENCODING numbers
"ziplist"
# 再添加一个新元素
127.0.0.1:6379> ZADD numbers 3.14 pi
(integer) 1
# 对象包含的元素数量变为 129 个
127.0.0.1:6379> ZCARD numbers
(integer) 129
# 编码已改变
127.0.0.1:6379> OBJECT ENCODING numbers
"skiplist"

  

以下代码则展示了有序集合对象因为元素的成员过长而引发编码转换的情况:

# 向有序集合添加一个成员只有三字节长的元素
127.0.0.1:6379> ZADD blah 1.0 www
(integer) 1
127.0.0.1:6379> OBJECT ENCODING blah
"ziplist"
# 向有序集合添加一个成员为 66 字节长的元素
127.0.0.1:6379> ZADD blah 2.0 oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
(integer) 1
# 编码已改变
127.0.0.1:6379> OBJECT ENCODING blah
"skiplist"

  

有序集合命令的实现

因为有序集合键的值为哈希值,所以用于有序集合键的所有命令都是针对哈希对象来构建的,表1-11列出了其中一部分有序集合键命令,以及这些命令在不同编码的哈希对象下的实现方法

表   8-11有序集合命令的实现方法
命令ziplist编码的实现方法zset编码的实现方法
ZADD调用ziplistInsert函数, 将成员和分值作为两个节点分别插入到压缩列表先调用zslInsert函数,将新元素添加到跳跃表,然后调用dictAdd 函数,将新元素关联到字典
ZCARD调用ziplistLen函数,获得压缩列表包含节点的数量,将这个数量除以2得出集合元素的数量访问跳跃表数据结构的length属性, 直接返回集合元素的数量
ZCOUNT遍历压缩列表,统计分值在给定范围内的节点的数量遍历跳跃表,统计分值在给定范围内的节点的数量
ZRANGE从表头向表尾遍历压缩列表,返回给定索引范围内的所有元素从表头向表尾遍历跳跃表,返回给定索引范围内的所有元素
ZREVRANGE从表尾向表头遍历压缩列表,返回给定索引范围内的所有元素从表尾向表头遍历跳跃表,返回给定索引范围内的所有元素
ZRANK从表头向表尾遍历压缩列表,查找给定的成员,沿途记录经过节点的数量,当找到给定成员之后,途经节点的数量就是该成员所对应元素的排名从表头向表尾遍历跳跃表,查找给定的成员,沿途记录经过节点的数量,当找到给定成员之后,途经节点的数量就是该成员所对应元素的排名
ZREVRANK从表尾向表头遍历压缩列表,查找给定的成员,沿途记录经过节点的数量,当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名从表尾向表头遍历跳跃表,查找给定的成员,沿途记录经过节点的数量,当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名
ZREM遍历压缩列表,删除所有包含给定成员的节点,以及被删除成员节点旁边的分值节点遍历跳跃表,删除所有包含了给定成员的跳跃表节点。 并在字典中解除被删除元素的成员和分值的关联
ZSCORE遍历压缩列表,查找包含了给定成员的节点,然后取出成员节点旁边的分值节点保存的元素分值直接从字典中取出给定成员的分值

转载于:https://www.cnblogs.com/beiluowuzheng/p/9737243.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/426671.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java学习(17):巩固练习

//#任务 3 //#已知有三个人,张无忌,任盈盈,任我行。当前只知道任盈盈的年龄 //#可以被用户从控制台输入,并且用户可以告知任盈盈的年龄比张无忌的年龄小几岁, //#任我行年龄是张无忌和任盈盈年龄和还要大几岁&#xff…

mysql innodb redolog_MySQL · 引擎特性 · InnoDB redo log漫游(转)

前言InnoDB 有两块非常重要的日志,一个是undo log,另外一个是redo log,前者用来保证事务的原子性以及InnoDB的MVCC,后者用来保证事务的持久性。和大多数关系型数据库一样,InnoDB记录了对数据文件的物理更改&#xff0c…

Jenkins配置:添加用户和管理权限

Jenkins配置:添加用户和管理权限 参考文章:http://www.cnblogs.com/zz0412/p/jenkins_jj_14.html 今天给大家说说使用Jenkins专有用户数据库的配置,和一些常用的权限配置。 配置用户注册 在新安装好的jenkins中,默认是没有设置用户…

java学习(18):巩固练习

/任务 4 白大壮和白二壮是双胞胎兄弟, 白大壮的身高增加1厘米正好是白二壮的身高 ,白二壮体重正好是妹妹白无瑕体重,编写程序完成白大壮 和白二壮身高的计算并输出,并计算白无瑕的体重输出是多少/ import java.util.Scanner; publ…

java-appium-527进阶-1 UiAutomator12区别和封装

1.UiAutomator和UiAtumator2的区别: 1.1 UiAutomator1有关于id定位的策略 UiAutomator1 id定位在resourceid匹配失败时,会匹配contentDesc。 安卓会根据id进行3种情况的判断: 1.resourceId 如user_profile_icon2.accessibility id3.Strings.…

java学习(19):巩固练习

/任务 5 有三位老师,王老师,孙老师和小李老师, 王老师工龄最长(15年),孙老师工龄比王老师小3年, 小李老师工龄最短,是王老师和孙老师工龄和的二分之一 再除以2的余数正好是他的工龄,编写程序从控…

mysql分组获取其他字段_sqlserver group by后获取其他字段(多种方法)

大家都知道用group by的话,select 后面指定的字段必须与group by后面的一致。group by 只有个别字段,如果拿出其他未分组的字段信息呢?在网上搜了下,总结如下:使用了group by 之后,就要求select后面的字段包…

搜索引擎基础概念(1)—— 倒排索引

“ 吾有三剑,唯子所择;皆不能杀人,且先言其状。一曰含光,视之不可见,运之不知有。其所触也,泯然无际,经物而物不觉。二曰承影,将旦昧爽之交,日夕昏明之际,北面…

java学习(20):巩固练习

//用运算符判断2019是不是闰年 /①、普通年能被4整除且不能被100整除的为闰年。 (如2004年就是闰年,1901年不是闰年)地球公转示意图②、世纪年能被400整除的是闰年。 (如2000年是闰年,1900年不是闰年) ③、 对于数值很大的年份能整除3200,但同…

汇编软件的安装与实验一

软件的安装在课程邮箱里有详尽的介绍,但在安装调试的时候还是出了一点小问题,创建虚拟盘符的时候,我将masm文件夹前面套上了一层名为masm文件夹,导致虚拟盘符创建之后无法使用debug。 随后就是实验 实验1.1写入程序段并且执行 a命…

upc 9519 New Game

New Game 时间限制: 1 Sec 内存限制: 128 MB Special Judge提交: 157 解决: 53[提交] [状态] [讨论版] [命题人:admin]题目描述 Eagle Jump公司正在开发一款新的游戏。泷本一二三作为其员工,获得了提前试玩的机会。现在她正在试图通过一个迷宫。这个迷宫有一些特…

java学习(21):移位运算符

//移位运算符 public class test{ public static void main(String[] args){ int num3; //向左移位 System.out.println(“移位之前的二进制为”Integer.toBinaryString(num)); int moveleftnum<<2; System.out.println(“移位之后的值为”moveleft); //向右移位 int num…

Datagridview绘制

#region 绘制private void dataGridView_main_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e){int status_column_index 14;//会诊状态所在列DataGridViewRow row dataGridView_main.Rows[e.RowIndex];//获取行DataGridViewCell cell row.Cells[15];//按钮…

java学习(22):if语句

/任务 1&#xff1a;if语句 编写控制台java程序&#xff0c;使用Scanner 对象相关方法从控制台接收用户输入学生年龄&#xff0c; 如果输入的年龄大于18&#xff0c;则输出“你是一个成年人了&#xff0c;该有担当了&#xff01;/ import java.util.Scanner; public class test…

java学习(23):if..else

/任务2&#xff1a;if else 语句 编写控制台java程序&#xff0c;模拟银行取款的功能。 使用Scanner对象相关方法从控制台接收用户输入的银行卡账号和密码&#xff0c; 与预先定义好的银行卡账号密码相同则输出用户名密码正确&#xff0c;可以取款&#xff1b;如果账号或者密码…

android 面试汇总二

AnimationQ&#xff1a;Android中有哪几种类型的动画&#xff1f; 技术点&#xff1a;动画类型参考回答&#xff1a; 常见三类动画 View动画&#xff08;View Animation&#xff09;/补间动画&#xff08;Tween animation&#xff09;&#xff1a;对View进行平移、缩放、旋转和…

java学习(24):if..else...if

/任务3&#xff1a;if… else if…. else if….else语句 编写控制台java程序&#xff0c;模拟根据有多少钱买车的功能。 使用Scanner对象相关方法从控制台接收用户输入有多少钱&#xff0c; 单位为万。如果输入的钱数量大于等于100万&#xff0c;则输出“可以买奔驰” &#xf…

java学习(25):三目运算符

/任务4&#xff1a;三目运算符 编写控制台java程序&#xff0c; 使用Scanner对象相关方法从控制台接收两个整数&#xff0c;比较他们的大小/ import java.util.Scanner; public class test04{ public static void main(String[] args){ Scanner in new Scanner(System.in); Sys…

java学习(26):switch

/扩展练习&#xff1a; 任务5&#xff1a;switch语句 编写控制台Java程序&#xff0c;使用Scanner对象相关方法接收用户输入的年份和月份&#xff0c; 输出这个月有多少天。(提示:闰年计算方法为&#xff1a;能被4整除单不能被100整除&#xff1b;或者能被400整除就是闰年。)/ …

java学习(27):巩固练习

/1 使用Eclipse编写控制台应用程, 使用while循环在控制台打印10行10列的如下图形 □ □ □ □ □ □ □ □ □ □ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ □ □ □ □ □ □ □ □ □ □ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ □ □ □ □ □ □ □ □ □ □ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ …