Redis(七)对象
Redis用到的主要数据结构有简单动态字符串(SDS)、双端链表、字典、跳跃表、压缩列表、整数集合等。但是Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统。包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象。每种对象至少用到了一种前面的数据结构。
1、对象类型及编码
Redis数据库中新创建一个键值对时,至少会创建两个对象,分别用作键对象和值对象。
对象的结构体定义如下:
类型
对象的type
属性记录了对象的类型,属性值如下表:
键总是字符串对象,则值可以为上述所有对象。
对数据库键执行TYPE
命令时,返回的结果是值对象的类型,不同类型值对象的TYPE
命令输出如下表:
编码和底层实现
对象的ptr
指针指向对象的底层实现数据结构,而数据结构由对象的encoding
属性决定。encoding
属性记录了对象所使用的编码,即对象使用了什么数据结构作为对象的底层实现,每种类型的对象都至少使用了两种不同的编码:
使用OBJECT ENCODING
命令可以查看数据库键的值对象编码。
通过encoding
属性来设定对象所使用的编码,而不是为特定类型对象关联一种固定编码,极大提升了Redis的灵活性和效率。
2、字符串对象
**字符串对象的编码可以是int、raw
或者embstr
。即底层实现是整数、embstr编码的SDS和SDS。
- 如果字符串对象保存的是整数值,并且该整数值可以用
long
类型表示,那么字符串对象会将整数值保存在字符串对象结构的ptr
属性里面(将void*
转换为long
),并将字符串对象编码设置为int
。 - 如果字符串对象保存的是字符串值,并且长度大于32字节,将设置为简单动态字符串(SDS),编码设置为
raw
。 - 如果字符串对象保存的是字符串值,并且长度小于等于32字节,将设置为
embstr
编码方式。
embstr
编码专门用于保存短字符串的优化编码方式。和raw
编码方式一样,都使用redisObject
结构和sdshdr
结构;不同的是raw
编码会调用两次内存分配函数分别创建redisObject
结构和sdshdr
结构,embstr
编码只会调用一次内存分配函数来分配一块连续的空间,空间依次包含redisObject
和sdshdr
两个结构。
embstr
编码的字符串对象在执行命令时,产生的效果和raw
编码的一样。但有以下好处:
执行以下命令:
注意:可以用
long double
类型表示的浮点数在Redis中也是作为字符串值来保存的。
编码的转换
int
编码的字符串对象和embstr
编码的字符串对象在条件满足的情况下,会被转换为raw
编码的字符串对象。
- 对于
int
编码的字符串,如果对对象执行一些命令,使得这个对象保存的不再是整数值,而是一个字符串值,编码则会变为raw
。例如APPEND
命令。 - 因为Redis没有为
embstr
编码的字符串对象编写任何修改程序,所以embstr
编码的字符串对象实际上是只读的。执行任何修改命令时,编码就会改为raw
。
字符串命令的实现
部分字符串命令及在不同编码下的实现方法:
3、列表对象
列表对象的编码可以是ziplist
或者linkedlist
。即底层实现是压缩列表和双端链表。
ziplist
编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点保存了一个列表元素。如下图:
linkedlist
编码的列表对象使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素。
注意:
- 双端链表结构中嵌套了字符串对象,字符串对象是Redis五种类型的对象中唯一一种会被其他对象嵌套的对象。
- 上图是简化的字符串对象表示,真实情况如下图:
编码转换
当列表对象满足以下两个条件时,列表对象使用ziplist
编码:
- 列表对象保存的所有字符串元素的长度都小于64字节;
- 列表对象保存的元素数量小于512个。
不能同时满足条件的列表对象需要使用linkedlist
编码。
注意:以上两个条件的上限值可以修改。
对于使用ziplist
编码的列表对象来说,当以上任意一个条件不能被满足时,对象编码转换操作就会被执行。
列表命令的实现
下表列出了列表对象常见命令:
4、哈希对象
哈希对象的编码可以是ziplist
和hashtable
。即底层实现是字典和压缩列表。
压缩列表作为底层实现时,有以下特点:
- 保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,值的节点在后。
- 先添加的键值对会被放在压缩列表表头方向,后添加的在表尾方向。
字典作为底层实现时,有以下特点:
- 字典的每个键都是一个字符串对象,保存了键值对中的键。
- 字典的每个值都是一个字符串对象,保存了键值对中的值。
编码转换
当哈希表对象同时满足以下两个条件时,使用ziplist
编码:
- 哈希对象保存的键值对中键和值的字符串长度都小于64字节。
- 键值对数量小于512个。
哈希表命令实现:
5、集合对象
集合对象的编码可以是intset
或者hashtable
。即底层整数集合或者字典。
inset
编码的集合使用整数集合作为底层是实现:
hashtable
编码的集合使用字典作为底层对象,字典的每一个键都是一个字符串对象,包含一个集合元素,字典的值全部被设置为NULL。
编码转换
当集合对象同时满足以下两个条件是,使用intset
编码:
- 集合对象所有元素都是整数值
- 元素数量不超过512个。
集合命令的实现
6、有序集合对象
有序集合的编码可以是ziplist
或者skiplist
。即有序集合的底层实现是压缩列表或者跳表和字典。
ziplist
编码的有序集合对象使用压缩列表作为底层实现。每个集合元素使用两个紧挨一起的压缩列表结点保存,第一个节点保存元素成员,第二个节点保存元素的分值。
压缩列表内集合元素按分值从小到大进行排序。
skiplist
编码的有序集合对象使用zset
结构作为底层实现,一个zset
结构同时包含一个字典和一个跳表:
zet
结构中跳表按分值从小到大保存了所有集合元素:跳表节点的object属性保存了元素的成员,score属性保存了元素的分值。
zset
结构中的 字典为有序集合创建了一个从成员到分值的映射:字典的键保存了元素的成员,字典的值保存了元素的分值。即通过字典可以用O(1)复杂度查找给定成员的分值。
虽然zset
结构同时使用跳表和字典保存有序集合的元素,但这两种数据结构通过指针共享相同元素的成员和分值,所以不会造成额外的内存浪费。
编码转换
当有序集合对象同时满足以下两个条件时,使用ziplist
编码:
- 有序集合元素小于128个。
- 有序结合保存的元素成员长度都小于64字节。