在上一节学习Redis中的六种基础数据结构,但在Redis中并没有直接使用以上的数据结构实现键值对数据库,而是基于这些数据结构构建了一个对象系统:字符串对象(String)、列表对象(List)、哈希对象(Hash)、集合对象(Set)和有序集合对象(ZSet)。对于Redis数据库,键总是一个字符串对象,而值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象中的一种。
使用对象系统的好处:
(1)在执行命令时,根据对象类型判断一个对象是否可以执行给定的命令;
(2)针对不同的使用场景,为对象设置多种不同的数据结构,从而优化对象在不同场景下的使用效率;
(3)基于对象引用计数技术实现内存的回收;
(4)通过引用计数技术实现对象的共享;
在Redis中每个对象都由一个redisObject结构表示:
/*
* Redis 对象
*/
typedef struct redisObject {
// 类型
unsigned type:4;
// 编码
unsigned encoding:4;
// 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
// 引用计数
int refcount;
// 指向实际值的指针
void *ptr;
} robj;
redisObject的类型字段:
redisObject的编码字段:
redisObject不同类型和编码的对象:
一、字符串对象(String)
由上表可知,String对象的编码可以是int、embstr、raw。根据String对象保存值的不同,其使用的编码也不同:
1、如果保存的是整数值,并且这个整数值可以用long类型来表示,则整数值会保存在字符串对象结构中的ptr属性里(void * 转换为long),并将字符串对象的编码设置为int。
2、如果保存的是字符串值,并且这个字符串值长度小于等于39字节,则字符串对象使用embstr编码保存。
3、如果保存的是字符串值,并且这个字符串值长度大于39字节,则字符串使用raw编码保存。
embstr与raw编码的区别是什么呢?
(a)创建embstr编码字符串内存分配1次,创建raw字符串内存分配2次;
(b)释放embstr编码的字符串调用内存释放函数1次,释放raw编码的字符串调用内存释放函数2次;
(c)embstr编码的字符串对象的所有数据都保存在一块连续的内存里面,可以更好的利用缓存带来的优势;
为什么是39个字节呢?
embstr是一块连续的内存区域,由redisObject和sdshdr组成。其中redisObject占16个字节,当buf内的字符串长度是39时,sdshdr的大小为8+39+1=48,那一个字节是'\0'。加起来刚好64。(更详细的讲解可以参见知乎@lhcpig的解答)
typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ int refcount; void *ptr; } robj; struct sdshdr { unsigned int len; unsigned int free; char buf[]; };
Window环境下,在本地启动redis-server.exe,然后另启动一个redis-cli.exe。对String对象的操作如下:
二、列表对象(List)
列表对象的编码可以是ziplist或者linkedlist。ziplist使用压缩列表作为底层实现,linkedlist使用双端链表作为底层实现。根据列表中存放的值不同,编码也有所不同:
当列表对象所保存的所有字符串元素长度都小于64字节,并且元素数量小于512个,列表使用ziplist编码。不满足这两个条件的列表对象需要使用linkedlist编码。
三、哈希对象(Hash)
哈希对象的编码可以是ziplist或者是hashtable。ziplist底层使用压缩列表实现,而hashtable底层使用字典实现。根据哈希对象存放的值不同,所使用的编码也不同:
当哈希对象保存的所有键值对的键和值的字符串长度都小于64字节,并且键值对数量小于512个,哈希对象使用ziplist编码。不满足以上两个条件的哈希对象使用hashtable编码。
四、集合对象(Set)
集合对象的编码可以是intset或者hashtable。intset底层使用的是整数集合实现,hashtable底层使用字典实现。根据集合对象存放的值不同,集合对象所使用的编码也不相同:
当集合对象保存的所有元素都是整数值,并且对象保存的元素数量不超过512个,集合对象使用intset编码。不满足以上条件的使用hashtable编码。
五、有序集合对象(ZSet)
有序集合的编码可以是ziplist或者skiplist。ziplist底层是用压缩列表实现,skiplist使用zset结构作为底层实现。根据有序集合存放的值不同,对象的编码也不同:
有序集合保存的元素数量小于128个,并且所有元素成员长度都小于64个字节时,有序集合对象使用ziplist编码。不满足以上两个条件的采用skiplist编码。
参考文献
1、http://www.redis.net.cn/tutorial/3506.html
2、《Redis设计与实现》第二版---黄健宏
3、https://github.com/xingzhexiaozhu/redis-3.0-annotated
4、http://www.yiibai.com/redis/redis_strings.html