对象介绍
前面介绍的都是基本数据结构,Redis并没有直接使用这些基本数据结构,而是基于这些数据结构创建了一个对象系统,这个系统包含 字符串对象、 列表对象、 哈希对象、 集合对象和 有序集合对象这五种类型。
Redis通过引用计数实现了内存回收,同时还实现了对象共享机制。
Redis中的键和值都是对象。
Redis中的每个对象都由一个redisObject结构表示
,该结构中和
保存数据有关的3个属性分别是
type属性、
encoding属性和
ptr属性:
typedef struct redisObject {
// 类型,':'表示占用的bit数
unsigned type:4;
// 编码
unsigned encoding:4;
// 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
// 引用计数
int refcount;
// 指向实际值的指针
void *ptr;
} robj;
字符串对象
编码实现
- int:一个字符串对象保存的是整数,并且这个整数可以用long类型表示,那么就将这个整数保存在ptr中(将void*转换成long),并将字符串对象编码为int。
- raw:保存的是一个字符串值,并且这个字符串值的长度大于39字节,那么将用一个SDS来保存这个字符串,并将对象编码设置为raw。
- embstr:保存的是一个字符串值,但长度小于等于39字节,那么字符串对象将使用embstr编码方式。
raw和embstr的区别?
rmbstr编码是专门用于保存短字符串的一种优化编码方式,它只进行一次内存分配和回收,它的StringObject和SDS紧挨。而raw编码单独分配StringObject和SDS,需要两次内存分配和回收
。
编码转化
在int和embstr的条件不满足时
,会自动转化为raw编码。此外,对embstr执行任何修改命令时
,程序会将对象编码转换成raw,因为embstr没有任何操作函数,它是只读的
。
常用命令
命令 | 作用 |
---|---|
SET | 设置值 |
GET | 获取值 |
APPEND | 将给定字符串追加到本字符串末尾 |
INCRBYFLOAT | 将字符串转化为long double然后加上给定的数字 |
INCRBY | 对整数值进行加法计算,只能用于int编码 |
DECRBY | 对整数值进行减法计算,只能用于int编码 |
STRLEN | 返回字符串的长度 |
SETRANGE | 将字符串特定索引上的值设置为给定的字符 |
GETRANGE | 返回字符串指定索引上的字符 |
列表对象
编码实现
- 列表对象可以是ziplist或者linkedlist。
- Ziplist 是为了尽可能地节约内存而设计的特殊编码双端链表。它的所有元素在内存中紧挨在一起,却又不能通过下标访问。
- LinkedList是以链表的形式,每个节点是一个字符串对象。字符串对象是Redis五种类型的对象中唯一一种会被其他四种对象嵌套的对象。
编码转换
当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码:
- 列表对象保存的所有字符串元素的长度都小于64字节;
- 列表对象保存的元素数量小于512个;
不能同时满足这两个条件的,就需要使用linkelist编码。上面的两个值都是可以通过配置文件修改的。
常用命令
命令 | 作用 |
---|---|
LPUSH | 插入表头 |
RPUSH | 插入表尾 |
LPOP | 从表头返回并删除 |
RPOP | 从表尾返回并删除 |
LINDEX | 返回指定节点保存的元素 |
LLEN | 返回表的长度 |
LINSERT | 将值插入到指定位置 |
LREM | 删除包含了给定元素的节点 |
LTRIM | 删除表中所有不在指定索引范围内的节点 |
LSET | 将指定索引的值更新为指定的值 |
哈希对象
编码实现
- 哈希对象可用的编码是ziplist或者hashtable。
- ziplist方式的哈希对象使用ziplist作为底层数据结构,键和值节点紧挨在一起,插入到压缩列表末尾。
- hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存:字典的每个键都是一个字符串对象,对象中保存了键值对的键;字典的每个值都是一个字符串对象,对象中保存了键值对的值。
编码转换
当哈希对象可以同时满足以下两个条件时,哈希对象使用ziplist编码:
- 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
- 哈希对象保存的键值对数量小于512个;
不能满足这两个条件的哈希对象需要使用hashtable编码。上面两个上限值可以通过配置文件修改。
命令 | 作用 |
---|---|
HSET | 插入新的键值对 |
HGET | 查找给定键,并返回它的值 |
HEXISTS | 查找给定元素是否存在 |
HDEL | 删除指定键所在的键值对 |
HLEN | 返回键值对的数目 |
HGETALL | 返回所有的键和值 |
集合对象
集合对象的编码可以是intset或者hashtable。
intset编码使用整数集合作为底层实现,整数集合类似于一个数组,详细见上面介绍。
hashtable编码使用字典作为底层实现。
当下面两个条件都被满足时,使用intset编码,否则,使用hashtable编码
集合对象保存的所有元素都是整数值
集合对象保存的元素数量不超过512
第二个条件的上限可以通过配置文件修改。当两个条件中任一个不满足时,就会自动执行编码转换操作
常用命令
命令 | 作用 |
---|---|
SADD | 添加新元素 |
SCARD | 返回元素数量 |
SISMEMBER | 判断给定的元素是否在集合中 |
SMEMBERS | 返回所有元素 |
SRANDMEMBER | 随机返回集合中的一个元素 |
SPOP | 从列表中随机删除一个值 |
SREM | 删除所有给定的元素 |
有序集合对象
编码实现
- 有序集合的编码可以是ziplist或者skiplist。
- ziplist底层使用压缩列表,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),第二个节点保存元素的分值(Score)。压缩列表内按照分值升序排列。
skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表
:
/*
* 有序集合
*/
typedef struct zset {
// 字典,键为成员,值为分值
// 用于支持 O(1) 复杂度的按成员取分值操作
dict *dict;
// 跳跃表,按分值排序成员
// 用于支持平均复杂度为 O(log N) 的按分值定位成员操作
// 以及范围操作
zskiplist *zsl;
} zset;
正如源码注释所说,之所以同时采用两种数据结构来实现,是为了互相弥补对方的不足。zskiplist是为了弥补哈希表不能进行范围查询的缺点,而哈希表将单个元素的查询操作从zskiplist的O(logN)优化到了哈希表的O(1)。
虽然同时使用了两种结构,但是所有的元素节点都是共用的,即真正的节点只有一份,但是它被两种方式使用了。因此,同时使用两种数据结构,并没有浪费空间,且同时提高了范围查询和单值查询的效率。
有序集合的每个元素成员都是一个字符串对象,而每个元素的分支都是一个double类型的浮点数。
编码转换
当有序集合对象同时满足以下两个条件时,对象使用ziplist编码:
- 有序集合保存的元素数量小于128个;
- 有序集合保存的所有元素成员的长度都小于64字节。
不能满足任何一个,则使用skiplist编码。以上两个条件的上限值是可以在配置文件里修改的。当两个条件的任何一个不被满足时,程序会自动执行编码转换操作。
常用命令
命令 | 作用 |
---|---|
ZADD | 插入元素 |
ZCARD | 返回集合元素的数量 |
ZCOUNT | 统计分值在给定范围内的节点的数量 |
ZRANGE | 返回给定索引范围内的所有元素 |
ZREVRANGE | 倒序返回给定范围内的元素 |
ZRANK | 返回元素的集合中的排名 |
ZREVRANK | 返回元素在集合中的倒序排名 |
ZREM | 删除所有包含了给定成员的跳跃表节点 |
ZSCORE | 返回给定元素的分值 |