目前为止,我们介绍了 redis 中非常典型的五种数据结构,从 SDS 到 压缩列表,这都是 redis 最底层、最常用的数据结构,相信你也掌握的不错。

但 redis 实际存储键值对的时候,是基于对象这个基本单位的,并且往往一个对象下面对对应不同的底层数据结构实现以便于在不同的场景下切换底层实现提升效率。例如列表对象在元素不多情况话会使用压缩列表来实现以压缩内存,而在元素比较多的时候常规的双端链表进行实现。

下面我们就具体来看看 redis 中都有哪些对象,底层又对应哪些可供选择的数据结构。

一、Redis 对象结构定义

redis 为每个对象定义为如下数据结构:

typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS;
int refcount;
void *ptr;
} robj;

type 记录的是当前的对象类型,有以下几种类型:

#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 才会使用字符串对象存储数据。

  1. 字符串(raw):普通的字符串
  2. 整数(int):long 类型的整数值
  3. 短字符串(embstr ):短字符串

如果判定使用 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 数据库迈进~


关注公众不迷路,一个爱分享的程序员。

公众号回复「1024」加作者微信一起探讨学习!

每篇文章用到的所有案例代码素材都会上传我个人 github

https://github.com/SingleYam/overview_java

欢迎来踩!

Redis 的底层数据结构(对象)的更多相关文章

  1. Redis(二)--- Redis的底层数据结构

    1.Redis的数据结构 Redis 的底层数据结构包含简单的动态字符串(SDS).链表.字典.压缩列表.整数集合等等:五大数据类型(数据对象)都是由一种或几种数结构构成. 在命令行中可以使用 OBJ ...

  2. 深入理解Redis:底层数据结构

    简介 redis[1]是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorte ...

  3. Redis详解(四)------ redis的底层数据结构

    上一篇博客我们介绍了 redis的五大数据类型详细用法,但是在 Redis 中,这几种数据类型底层是由什么数据结构构造的呢?本篇博客我们就来详细介绍Redis中五大数据类型的底层实现. 1.演示数据类 ...

  4. Redis 详解 (四) redis的底层数据结构

    目录 1.演示数据类型的实现 2.简单动态字符串 3.链表 4.字典 5.跳跃表 6.整数集合 7.压缩列表 8.总结 上一篇博客我们介绍了 redis的五大数据类型详细用法,但是在 Redis 中, ...

  5. redis string底层数据结构sds

    redis的string没有采用c语言的字符串数组而采用自定义的数据结构SDS(simple dynamic string)设计 len 为字符串的实际长度  在redis中获取字符串的key长度的时 ...

  6. Redis 的底层数据结构(SDS和链表)

    Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件.可能几乎所有的线上项目都会使用到 Redis,无论你是做缓存.或是用作消息中间件,用起来很简单方便 ...

  7. Redis 的底层数据结构(字典)

    字典相对于数组,链表来说,是一种较高层次的数据结构,像我们的汉语字典一样,可以通过拼音或偏旁唯一确定一个汉字,在程序里我们管每一个映射关系叫做一个键值对,很多个键值对放在一起就构成了我们的字典结构. ...

  8. Redis 的底层数据结构(跳跃表)

    字典相对于数组,链表来说,是一种较高层次的数据结构,像我们的汉语字典一样,可以通过拼音或偏旁唯一确定一个汉字,在程序里我们管每一个映射关系叫做一个键值对,很多个键值对放在一起就构成了我们的字典结构. ...

  9. Redis 的底层数据结构(整数集合)

    当一个集合中只包含整数,并且元素的个数不是很多的话,redis 会用整数集合作为底层存储,它的一个优点就是可以节省很多内存,虽然字典结构的效率很高,但是它的实现结构相对复杂并且会分配较多的内存空间. ...

随机推荐

  1. Yii ActiveRecord用法记录备忘

    ActiveRecord 使用方法 Example1 in查询 $criteria = new CDbCriteria(); $criteria->select = $select; $crit ...

  2. MongoDB一次节点宕机引发的思考(源码剖析)

    目录 简介 日志分析 副本集 如何实现 Failover 心跳的实现 electionTimeout 定时器 业务影响评估 参考链接 声明:本文同步发表于 MongoDB 中文社区,传送门: http ...

  3. 干货:.net core实现读取自定义配置文件,有源代码哦

    看好多人不懂在.NET CORE中如何读取配置文件,我这里分了两篇,上一篇介绍了怎样通过appsettings.json配置读取文件信息.这一篇教大家自定义配置文件: 1.在项目下创建配置文件 { & ...

  4. java和Jvm目录

    回到占占推荐博客索引 主要介绍java基础知识,非框架类及JVM相关的内容文章 java和Jvm目录 Java~关于开发工具和包包 Java~类,抽象类和接口 Java~时间戳小知识 Java~命名规 ...

  5. 关于php注释那些事

    代码注释的作用  --- 为自己,也为别人. 永远不要过于相信自己的理解力!当你思路通畅,思如泉涌,进入编程境界,你可以很流畅的实现某个功能,但这种一泻千里的流畅可能只停留在了当时的状态.当你几个月, ...

  6. git log 附加命令归纳

    git查看历史记录的时候查看每次提交的内容差异: git log -p git限制显示的条数 : git log -p -2 [-2] git简单显示每次提交做了哪些改动: git log --sta ...

  7. 在mac上用parallels创建双windows虚拟机调试windows驱动

    先创建两个windows 7 虚拟机,一个装windbg作为调试机,一个被调试 1 调试机 1 先装windbg https://developer.microsoft.com/en-us/windo ...

  8. Pandas 计算工具介绍

    # 导入相关库 import numpy as np import pandas as pd 统计函数 最常见的计算工具莫过于一些统计函数了.首先构建一个包含了用户年龄与收入的 DataFrame i ...

  9. 可爱精灵宝贝:dp

    拒绝听搜索.etc水过的.数据太弱了(尽管考场上我凭借数据太水骗了好多分) 我讲的思路和下发的题解一样.(因为我不会所以只能颓它啊) 首先你要相信这题精灵就100个,真的只有100个,这次数据范围没错 ...

  10. CSPS模拟测试59

    这场考得我心态爆炸......... 开场T1只会$n^{2}$,然后发现bfs时每个点只需要被更新一次,其他的更新都是没用的. 也就是说,我们可以只更新还没被更新的点? 于是我先YY了一个链表,发现 ...