之前我们学习了Redis中使用到的数据结构,但是Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统。该系统包含字符串对象,列表对象,哈希对象,集合对象和有序集合对象这五中类型的对象。
一.对象的类型和编码
Redis使用对象来表示数据库中的键和值,每次在数据库中新创建一个键值对时,至少会创建两个对象,一个对象用作键值对的键(键对象),一个对象用作键值对的值(值对象)。
Redis中的每个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是type,encoding,ptr.
结构:
typedf struct redisObject{
//类型
unsigned type:4;
//编码
usigned encoding:4;
//执行底层实现数据结构的指针
void *ptr;
}
1.类型
type记录对象的类型,这个属性的值可以时下表中常量中的一个
对于Redis数据库保存的键值对来说,键总是一个字符串对象,而值可以是字符串对象,列表对象,哈希对象,集合对象,或有序集合对象中的一种。
字符串键:该键对象的值为字符串对象
列表键:该键对象的值为列表对象
2.编码和底层实现
encoding属性记录了对象使用的编码,也就是说指明这个对象使用了什么数据结构作为对象的底层实现。该属性值可以是下列中的一个。ptr指针指向对象的底层实现数据结构。
Redis中每种类型的对象都至少使用了两种不同的编码(在底层至少使用了两种数据结构)。
通过encoding属性来设定对象使用的编码,可以提高Redis的灵活性和效率。这样Redis可以根据不同的使用场景来为一个对象设置不同的编码,优化对象在某一个场景下的效率。(如果是为特定类型的对象关联一种特定的编码,这样的就太死板了。这种设计的思想可以学习下)
二,字符串对象
1. 字符串对象的编码有三种,int, raw, embstr
如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面,并将字符串对象的编码设置为int。
如果字符串对象保存的时一个字符串值,并且这个字符串值的长度小于等于39字节,那么字符串对象使用embstr编码方式来保存这个字符串值
embstr编码是专门用来保存短字符串的一种优化编码方式,通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构。
如果字符串对象保存的时一个字符串值,并且这个字符串值的长度大于39字节,那么字符串对象使用raw编码方式来保存这个字符串值
raw编码也是使用redisObject结构和sdshdr结构来表示字符串对象,但是raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构。
对比:
embstr编码创建字符串对象只需要调用一次内存分配函数
释放embstr编码的字符串对象只需要调用一次内存释放函数
embstr编码的字符串对象的所有数据都保存在一块连续的内存中,能够更好的利用缓存带来的优势
对于浮点数,redis保存的类型是字符串类型的值;在需要取浮点数进行数学运算时,会将字符串类型的浮点数值取出来转回浮点数值,执行完计算后,再转换成字符串保存。
2.编码的转换
int编码和embstr编码的字符串对象在合适的条件下会转换成raw编码的字符串对象。
当int编码的字符串对象进行某些操作后不在是整数值时,会转换为raw编码的字符串
embstr编码的字符串是只读的,他没有任何修改的方法函数,所以放对embstr编码的字符串对象执行修改命令后,都会转换为raw编码的字符串
三,列表对象
1.列表对象的编码可以是ziplist或者linkedlist。
ziplist是一种压缩链表,它的好处是更能节省内存空间,因为它所存储的内容都是在连续的内存区域当中的。当列表对象元素不大,每个元素也不大的时候,就采用ziplist存储。但当数据量过大时就ziplist就不是那么好用了。因为为了保证他存储内容在内存中的连续性,插入的复杂度是O(N),即每次插入都会重新进行realloc。如下图所示,对象结构中ptr所指向的就是一个ziplist。整个ziplist只需要malloc一次,它们在内存中是一块连续的区域。
linkedlist是一种双向链表。它的结构比较简单,节点中存放pre和next两个指针,还有节点相关的信息。当每增加一个node的时候,就需要重新malloc一块内存。
注意:上面链表中的StringObject其实是字符串对象的简化表示,每个StringObject都是一个包含了字符串值的字符串对象,
比如:"three"这个StringObject,其实是embstr编码格式的字符串对象:
2.编码转换
列表对象使用ziplist编码的条件(同时满足):
列表对象保存的所有字符串元素的长度都小于64字节
列表对象保存的元素数量小于512个,
对于使用ziplist编码的列表对象来说,有一个条件不满足时,对象的编码转换操作就会被执行。原本保存在压缩列表里的所有列表元素都会被转移并保存到双向链表里,对象的编码也会从ziplist变为linkedlist
注意:这两个条件的值是可以修改的,修改配置文件conf的两个属性即可:
list-max-ziplist-value
list-max-ziplist-entries
四,哈希对象
哈希对象的编码可以是ziplist或者hashtable
1.ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入哈希对象时,程序先将保存了键的节点推入到压缩列表尾部,然后再把保存了值的节点推入到压缩列表的尾部。那么,保存了同一个键值对的两个节点都挨在一起,保存键的节点在前,保存值的节点在后。
2.hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存
3.编码转换
当哈希对象同时满足下面两个条件,哈希对象使用ziplist编码,否则使用hashtable编码:
哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
哈希对象保存的键值对数量小于512个
4.注意:
这两个条件的上限值是可以修改的,在conf配置文件中:
hash-max-ziplist-value
hash-max-ziplist-entries
五,集合对象