Redis:只刷面试题,怎可能进大厂,多理解原理(基于五种基本数据结构的对象系统)

aas 在前面的两篇博客中,我们陆续介绍了Redis 用到的所有主要数据结构,比如 简单动态字符串( SDS ) 、双端链表、字典、压缩列表、整数集合等等 。Redis 并没有直接使用这些数据结构来实现键值对数据库,而是 基于这些数据结构创建了一个对象系统, 这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象。
asdsadasdasdasdsadasdasdasdsadassdasdsasdsadsdasdasdsadasdasdsadasdsadassadasdas————《Redis设计与实现》


对象带来的好处?

asdasa ①、Redis 可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令。

asdasa ②、可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。

asa对象带来的相关计数:

asdasa ①、==Redis 的对象系统是基于 引用计数技术 的内存回收机制。

asdasa ②、 Redis 还通过 引用计数技术 实现了 对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一个对象来节约内存。

as dasa③、Redis 的对象带有 访问时间记录信息,该信息可以用于计算数据库键的空转时长

对象的类型与编码

asdasa Redis 使用对象来表示数据库中的键和值:(键对象)(值对象)。

asdasa 每个对象都由一个 redisObjet 结构表示,该结构中和保存数据有关的三个属性分别是 type 属性、encoding 属性和 ptr 属性:
在这里插入图片描述
asa type类型:

asdasa 对于Redis 数据库保存的键值对来说, 键总是一个字符串对象; 值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种。TYPE 命令可以查询数据库键对应的值对象的类型
在这里插入图片描述
asa ptr指针、encoding属性:

asdasa 对象的 ptr 指针指向对象的底层实现数据结构,而这些数据结构由对象的 encoding 属性决定。
在这里插入图片描述
asdasa 每种类型的对象都至少使用了 两种 不同的编码:(使用OBJECT ENCODING 命令可以查看一个数据库键的值对象的编码)
在这里插入图片描述
asdasa注:通过encoding 属性来设定对象所使用的编码而不是为特定类型的对象关联一种固定的编码,极大地提升了Redis 的灵活性和效率,因为Redis 可以根据不同的使用场景来为一个对象设置不同的编码,从而优化对象在某一场景下的效率。


字符串对象的编码:

asa 字符串对象的编码一共有三种:①、int ②、raw ③、embsr

asdasa ①、如果一个字符串对象保存的是整数值,并且这个整数值可以用 long 类型来表示,那么字符串对象会将整数值保存在字符串对象结构的 ptr 属性里面:
asdadssaeg: redis> SET number 10086
在这里插入图片描述
asdasa ②、如果字符串对象保存的是一个字符串值,并且字符串值的长度 > 32 字节将使用一个 简单动态字符串( SDS ) 来保存这个字符串值,并将对象的编码设置为raw。
asdadssaeg: redis> SET story “Long, long ago the re lived a king … . "
在这里插入图片描述
asdasa③、如果字符串对象保存的是一个字符串值,并且字符串值的长度 ≤ 32 字节将使用 embstr 编码的方式 来保存这个字符串值。
assa[注]:
asddssa①、embstr 编码是专门用于保存短字符串的一种优化编码方式
asddssa②、embstr 编码产生的效果和 raw 编码的产生的效果是相同的。我们进行比较:
assaddssa⒈embstr 编码将创建字符串对象所需的内存分配次数从raw 编码的 两次降低为一次。
assaddssa释放embstr 编码的字符串对象只需要调用一次内存释放函数,而释放raw 编码的字符串对象需要调用两次内存释放函数。
assaddssa⒊因为embstr 编码的字符串对象的 所有数据都保存在一块连续的内存里面,所以这种编码的字符串对象比起raw 编码的字符串对象 能够更好地利用缓存带来的优势
asddssaeg:redis> SET msg " hello”
在这里插入图片描述
assa[注]:long double 类型表示的浮点数在Redis 中也是作为字符串值来保存的
在这里插入图片描述
asa 一定条件下int、embstr 编码的字符串可以转换为 raw 编码的字符串对象:

asdasa ①、int --> raw:向对象执行了一些命令,使得这个 对象保存的不再是整数值,而是一个字符串值。

asdasa ②、embstr --> raw:因为Redis 没有为 embstr 编码的字符串对象编写任何相应的修改程序(只有 int 编码的字符串对象和raw 编码的字符串对象有这些程序),所以embstr 编码的字符串对象实际上是只读的。当我们对embstr 编码的字符串对象执行任何修改命令时,程序会先将对象的编码从embstr 转换成raw, 然后再执行修改命令因为这个原因, embstr编码的字符串对象在执行修改命令之后,总会变成一个raw 编码的字符串对象。


列表对象的编码:

asa 字符串对象的编码一共有两种:①、ziplist ②、linkedlist.

asdasa ①、如果ziplist 编码的列表对象使用压缩列表作为底层实现, 则每个压缩列表节点保存了一个列表元素。

asdasaeg:redis> RPUSH numbers 1 "three " 5
在这里插入图片描述
asdasa ②、如果用 linkedlist 编码的列表对象使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素。

asdasaeg:redis> RPUSH numbers 1 "three " 5
在这里插入图片描述
asdasa【注】:linkedlist 编码的列表对象在底层的双端链表结构中包含了多个字符串对象,这种嵌套字符串对象的行为在稍后介绍的哈希对象、集合对象和有序集合对象中都会出现,字符串对象是Redis 五种类型的对象中唯一一种会被其他四种类型对象嵌套的对象.

asa 列表对象使用ziplist编码的条件:

asdassa ①、列表对象保存的所有 字符串元素的长度都<64 字节;

asdassa ②、列表对象保存的 元素数量<512 个;

asdasa【注】:这两个值可以修改,具体看配置文件。当使用 ziplist 编码所需的两个条件的任意一个不能被满足时,对象的编码转换操作就会被执行,原本保存在压缩列表里的所有列表元素都会被转移并保存到双端链表里面,对象的编码也会从ziplist 变为linkedlist。


哈希对象的编码:

asa哈希对象的编码一共有两种:①、ziplist ②、hashtable.

asdasa ①、ziplist 编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表表尾。

asdadsasa保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后;

asdadsasa先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向。
asdadsasaeg: redis> HSET profile name “Tom”
在这里插入图片描述
asdasa ②、hashtable 编码的哈希对象使用字典作为底层实现

asdadsasaeg: ⅰ、redis> HSET profile name “Tom” sasaⅱ、redis> HSET profile career "Programmer
在这里插入图片描述
asa 哈希对象使用ziplist编码的条件:

asdassa ①、哈希对象保存的所有键值对的键和值的字符串元素的长度都<64 字节;

asdassa ②、键值对数量<512 个;

asdasa【注】:这两个值可以修改,具体看配置文件。除了键的长度太大会引起编码转换之外,值的长度太大也会引起编码转换。


集合对象的编码:

asa哈希对象的编码一共有两种:①、intset ②、hashtable.

asdasa ①、intset 编码的集合对象使用整数集合作为底层实现

asdadsasaeg: redis> SADD numbers 1 3 5
在这里插入图片描述
asdasa ②、hashtable 编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为NULL 。

asdadsasaeg: redis> SADD fruits "apple " “banana” "cherry "
在这里插入图片描述
asa 集合对象使用intset编码的条件:

asdassa ①、集合对象保存的所有元素都是整数值;

asdassa ②、集合对象保存的元素数量 ≤ 512 个。

asdasa【注】:第②个条件的值可以修改,具体请看配置文件。


有序集合对象的编码:

asa哈希对象的编码一共有两种:①、ziplist ②、skiplist.

asdasa ①、ziplist 编码的压缩列表对象使用 压缩列表 作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member), 而第二个元素则保存元素的分值(score)。压缩列表内的集合元素 按分值从小到大进行排序,分值较小的元素被放置在靠近表头的方向,而分值较大的元素则被放置在靠近表尾的方向。

asdadsasaeg: redis> ZADD price 8.5 apple 5 .0 banana 6 .0 cherry
在这里插入图片描述
asdasa ②、skiplist 编码的有序集合对象使用 zset 结构作为底层实现。 一个zset 结构同时包含一个字典和一个跳跃表。
asdasadasdsaⅰ、Zset 结构中的跳跃表按分值从小到大保存了所有集合元素。跳跃表节点的 object 属性保存了元素的成员,而跳跃表节点的score 属性则保存了元素的分值。 通过这个跳跃表,程序可以对有序集合进行范围型操作。
asdasadasdsaⅱ、Zset 结构中的diet 字典为有序集合创建了一个从成员到分值的映射, 字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值则保存了元素的分值。通过这个字典, 程序可以用0(1) 复杂度查找给定成员的分值,
ssdsasa【注】:
asdsdsadasa①、虽然zset 结构同时使用跳跃表和字典来保存有序集合元素,但这两种数据结构都会通过指针来共享相同元素的成员和分值,所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值,也不会因此而浪费额外的内存。
asdsdsadasa②、为什么有序集合需要同时使用跳跃表和字典来实现? 理论上,有序集合可以单独使用宇典或者跳跃表的其中一种数据结构来实现,但无论单独使用宇典还是跳跃表,在性能上对比起同时使用宇典和跳跃表都会有所降低。两方面:

asdsddassadasaⅰ、如果只用宇典来实现有序集合,虽然查找的复杂度为O(1) ,但如果进行排序, 完成这种排序需要复杂度为==O(NlogN) ==。
asdsddassadasaⅱ、如果只使用跳跃表来实现有序集合、跳跃表执行范围型操作的所有优点都会被保留、但查找操作的复杂度为O(logN)。

asdadsasaeg: redis> ZADD price 8.5 apple 5 .0 banana 6 .0 cherry
在这里插入图片描述
asa 有序集合对象使用ziplist编码的条件:

asdassa ①、有序集合保存的元素数量 <128 个;

asdassa ②、有序集合保存的所有元素成员的长度都<64 字节;

asdasa【注】:第②个条件的值可以修改,具体请看配置文件。当使用ziplist 编码所需的两个条件中的任意一个不能被满足时,就会执行对象的编码转换操作,原本保存在压缩列表里的所有集合元素都会被转移并保存到zset 结构里面,对象的编码也会从ziplist 变为 skiplist。


类型检查的实现

asa在执行一个类型特定的命令之前, Redis 会先检查输入键的类型是否正确,然后再决定是否执行给定的命令。

asdadsasaeg: 对于LLEN 命令来说:
在这里插入图片描述


多态命令的实现:

asaRedis 除了会根据值对象的类型来判断键是否能够执行指定命令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令。

asdadsasaeg: 对于LLEN 命令来说:
在这里插入图片描述


内存回收:

asa因为C 语言并不具备自动内存回收功能,所以Redis 在自己的对象系统中构建了一个引用计数技术实现的内存回收机制。每个对象的引用计数信息由redisObject 结构的refcount 属性记录.

asdadsasaeg:
在这里插入图片描述
asdadsasa用法:

asdaddsasasa①、在创建一个新对象时, 引用计数的值会被初始化为;

asdaddsasasa②、当对象被一个新程序使用时,它的引用计数值会被增一;

asdaddsasasa③、当对象不再被一个程序使用时,它的引用计数值会被减一;

asdaddsasasa④、当对象的引用计数值变为0 时,对象所占用的内存会被释放。

asdadsasa修改计数 API:
在这里插入图片描述
asdadsasa对象共享: 对象的引用计数属性还带有对象共享的作用。

asdadsasa为什么Redis 不共享包含字符串的对象?

asdaddsasasa当服务器考虑将一个共享对象设置为键的值对象时,程序需要先检查给定的共享对象和键想创建的目标对象是否完全相同,只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象用作键的值对象,而一个共享对象保存的值越复杂,验证共享对象和目标对象是否相同所需的复杂度就会越高,消耗的CPU 时间也会越多:

asdaddsdsaasasa①、如果共享对象是保存整数值的字符串对象,那么验证操作的复杂度为0(1);

asdaddsdsaasasa②、如果共享对象是保存宇符串值的字符串对象,那么验证操作的复杂度为O(N);

asdaddsdsaasasa③、如果共享对象是包含了多个值(或者对象的)对象,比如列表对象或者哈希对象,那么验证操作的复杂度将会是O(N2) 。
asdaddsasasa因此,尽管共享更复杂的对象可以节约更多的内存,但受到CPU 时间的限制,Redis 只对包含整数值的字符串对象进行共享。


对象的空转时长:

asaredisObject 结构包含的最后一个属性为 lru 属性,该属性记录了对象最后一次被命令程序访问的时间。空转时长较高的那部分键会优先被服务器释放,从而回收内存。

总结:

asa①、Redis 数据库中的每个键值对的键和值都是一个对象

asa②、Redis 共有字符串、列表、哈希、集合、有序集合五种类型的对象,每种类型的对象至少都有两种或以上的编码方式,不同的编码可以在不同的使用场景上优化对象的使用效率。

asa③、服务器在执行某些命令之前,会先检查给定键的类型能否执行指定的命令,而检查一个键的类型就是检查键的值对象的类型。

asa④、Redis 的对象系统带有引用计数实现的内存回收机制,当一个对象不再被使用时,该对象所占用的内存就会被自动释放

asa⑤、Redis 会共享值为 0 到9999 的字符串 对象(用来共享)。

asa⑥、对象会记录自己的最后一次被访问的时间,这个时间可以用于计算对象的空转时间。

💖感谢各位暴击三连💖

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值