目前为止,咱们介绍了 redis 中很是典型的五种数据结构,从 SDS 到 压缩列表,这都是 redis 最底层、最经常使用的数据结构,相信你也掌握的不错。java
但 redis 实际存储键值对的时候,是基于对象这个基本单位的,而且每每一个对象下面对对应不一样的底层数据结构实现以便于在不一样的场景下切换底层实现提高效率。例如列表对象在元素很少状况话会使用压缩列表来实现以压缩内存,而在元素比较多的时候常规的双端链表进行实现。git
下面咱们就具体来看看 redis 中都有哪些对象,底层又对应哪些可供选择的数据结构。程序员
redis 为每一个对象定义为以下数据结构:github
typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; int refcount; void *ptr; } robj;
type 记录的是当前的对象类型,有如下几种类型:redis
#define OBJ_STRING 0 /*字符串对象*/ #define OBJ_LIST 1 /*列表对象*/ #define OBJ_SET 2 /*集合对象*/ #define OBJ_ZSET 3 /*有序集合对象*/ #define OBJ_HASH 4 /*哈希对象*/
encoding 记录的是当前对象使用的哪一种底层数据结构实现的,有如下类型可供选择:算法
#define OBJ_ENCODING_RAW 0 /* SDS 字符串 */ #define OBJ_ENCODING_INT 1 /* 整数 */ #define OBJ_ENCODING_HT 2 /* 字典结构 */ #define OBJ_ENCODING_ZIPMAP 3 /* 压缩map,已经废弃 */ #define OBJ_ENCODING_LINKEDLIST 4 /* LinkedList 双端链表,废弃了 */ #define OBJ_ENCODING_ZIPLIST 5 /* 压缩列表 */ #define OBJ_ENCODING_INTSET 6 /* 整数集合 */ #define OBJ_ENCODING_SKIPLIST 7 /* 跳跃表 */ #define OBJ_ENCODING_EMBSTR 8 /* 短字符串 */ #define OBJ_ENCODING_QUICKLIST 9 /* 压缩链表和双向链表组成的快速列表 */
8 和 9 咱们遇到时在介绍,这里暂时不作介绍。数据库
lru 记录的是上一个当前对象实例被访问的时间,它用做计算对象空转时长,空转时长过大的对象会被 redis 优先释放内存。编程
refcount 记录的是对象的引用计数,引用计数算法是不少编程语言中管理对象是否应该被销毁的依据,和它相似的典型的 Java 中可达性分析算法,都是用于计数当前对象是否依然被使用,以便释放内存。缓存
ptr 指针指向的是实际实现当前对象的数据结构首地址。微信
以上就是 redisObject 数据结构的基本解释,下面咱们看具体的对象分别会在什么状况下切换不一样的底层实现。
字符串对象有三种 encoding 值,也就是只有这三种状况,redis 才会使用字符串对象存储数据。
若是断定使用 raw 编码,那么 redis 的 ptr 指针将会指向一个 SDS 结构,若是肯定使用 int 编码,那么会将 redisObject 中 ptr 类型由 void* 变成 long,继而分配 robj 内存。
当字符串的长度小于 39 个字节时,会采用 embstr 这种编码,embstr 其实也是使用 SDS 进行存储,区别于 raw 编码的是,后者会将 robj 和 ptr 指向的 SDS 分配在连续的内存块,惟一的好处是分配和释放内存都只须要一次操做便可完成,再一个是由于数据相邻,有可能一次加载 robj 的时候,CPU 将后面的 embstr 也加载进缓存,等到访问的时候就能够直接从缓存中访问。
可是,咱们看一个例子:
hello 本来是以 int 编码存储的,可是咱们执行 append 命令添加了字符串以后,它变成了 raw 编码。
这实际上是 redis 的一种编码换换,当 hello 再也不适合使用 int 编码继续存储的时候,会进行一个编码转换。
列表对象有两种编码,压缩列表 ziplist 和 linkedlist。咱们以前说过压缩列表的推荐应用场景,少许整数或字符串的时候能够用压缩列表来节省内存空间,而大数据量的节点则推荐使用普通的双端链表进行实现。
可是实际上,redis 的较新版本已经使用一种叫 quicklist 的快速列表整合 ziplist 和 linkedlist 做为列表对象的实现了。它将全部的节点分段拆分,每一份又使用压缩列表进行压缩,不一样段之间使用双向指针链接。
集合对象也有两种编码,整数集合 intset 和 字典 hashtable。默认状况下,当集合中有且仅有整型数据,且不超过 512 个,那么 redis 会使用整数集合 intset 进行集合存储,其他状况 redis 则构建字典进行集合数据存储。
顺便给你们复习下 intset 的无重复性、顺序性的特性,重复的元素是插入不进去的,由于插入以前会经过二分查找查找是否存在该元素,若是存在则拒绝插入操做。
固然了,若是集合中元素个数超过 512,那么 redis 就转而使用字典结构进行数据存储,具体实例就再也不演示了。
有序集合对象一样使用两种编码 ziplist 和 skiplist,可能你又见到压缩列表的身影了,足以见得,压缩列表是一个很是优秀的数据结构。
一样,当有序集合中包含少许元素的时候,redis 会优先使用压缩列表进行存储,反之选择跳跃表。
sadd 命令的标准语法是:
ZADD KEY_NAME SCORE1 VALUE1.. SCOREN VALUEN
每个元素都会对应一个分值,skiplist 自己的实现就须要这个分值进行元素的存储排序,有的时候有序集合会使用压缩列表进行实现,那么也须要这个分值来有序的压缩元素,这也是压缩列表页能够实现有序集合的缘由。
这里补充一下,虽说 redis 的有序集合是跳表实现的,这句话不错,但有失偏驳。
typedef struct zset { dict *dict; zskiplist *zsl; } zset;
准确来讲,redis 中的有序集合是由咱们以前介绍过的字典加上跳表(组合起来就是zset)实现的,字典中保存的数据和分数 score 的映射关系,每次插入数据会从字典中查询,若是已经存在了,就再也不插入,有序集合中是不容许重复数据。
哈希对象的编码能够是 ziplist 或者 hashtable,没什么特殊的,再也不赘述。
以上,咱们总结了 redis 中五大对象结构,以及他们可选的底层实现数据结构,相信你也理解的不错,这将很是有助于咱们后面的学习。
下节开始,咱们向 redis 数据库迈进~