redis五大数据类型以及底层的数据结构

      懒是一个很好的托词,说的就像勤快了你就能干成什么大事一样

      开发人员在直接使用redis时,可以使用五种数据类型,然而这五种数据类型在底层又分别有着不同的实现,这也是面试中的高频考点之一。

redisObject

      redis中,常用的底层数据结构有:简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合、跳跃表等。
      而redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,而每种对象又通过不同的编码映射到不同的底层数据结构。
      该对象即为redisObject。根据我在网上搜到的文章,redisObject定义在redis.h文件中。但我下载的redis-6.2.6版本中,redisObject定义在server.h中。
      redis使用该对象来表示数据库中的键和值,每次当我们在Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的健(键对象),另一个对象用作键值对的值(值对象)。
      其中Redis的键对象都是字符串对象,而Redis的值对象主要有字符串、哈希、列表、集合、有序集合几种。其分别对应的内部编码和底层数据结构如下图所示:

redisObject – type

      Redis中的每个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是type属性、 encoding属性和ptr属性。

typedef struct redisObject{
	//类型
	unsigned type:4;
	//编码
	unsigned encoding:4;
	//指向底层数据结构的指针
	void *ptr;
}

      4位的type表示具体的(面向用户的)数据类型,即:字符串、列表、集合、有序集合、字典。2^4 = 8足以表示这些类型。

/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

      在redis-6.2.6中,则变成了:

/* The actual Redis Object */
#define OBJ_STRING 0    /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */

redisObject – encoding

typedef struct redisObject{
	//类型
	unsigned type:4;
	//编码
	unsigned encoding:4;
	//指向底层数据结构的指针
	void *ptr;
}

      4位的encoding表示该类型的物理编码方式,同一种数据类型可能有不同的编码方式。在我百度到的博客中,写有目前Redis中主要有8种编码方式:

	#define REDIS_ENCODING_RAW 0     /* Raw representation */
    #define REDIS_ENCODING_INT 1     /* Encoded as integer */
    #define REDIS_ENCODING_HT 2      /* Encoded as hash table */
    #define REDIS_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
    #define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
    #define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
    #define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
    #define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */

      但在我下载的redis-6.2.6中,有如下10种编码方式
      

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

五种数据类型

字符串-string

      字符串类型是Redis最基础的数据结构,其他几种数据结构都是在字符串类型基础上构建的。字符串类型的值是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频)等。
      数字在redis中也是字符串。

内部结构

       字符串底层的数据结构实现主要有两种:int和简单动态字符串 —- SDS。
       SDS,和C语言的字符串不一样。C字符串仅仅作为字符串字面量,用在一些无需对字符串进行修改的地方,如打印日志。
       如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成long),并将字符串对象的编码设置为int:

typedef struct redisObject{
	//类型
	unsigned type:4;  		// #define OBJ_STRING 0
	//编码
	unsigned encoding:4;	// #define OBJ_ENCODING_INT 1
	//指向底层数据结构的指针
	void *ptr;				// long
}

在这里插入图片描述

我在网上查到的资料,有说32字节的,有说44字节的,我也懒得没去看哪个版本开始改变的。在我下载的redis-6.2.6中,是44字节,object.c的源码如下:

/* Create a string object with EMBSTR encoding if it is smaller than
 * OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
 * used.
 *
 * The current limit of 44 is chosen so that the biggest string object
 * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

      如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于44字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为raw;如果小于44字节,就将编码方式设置为embstr。

typedef struct redisObject{
	//类型
	unsigned type:4;  		// #define OBJ_STRING 0
	//编码
	unsigned encoding:4;	// embstr or raw
	//指向底层数据结构的指针
	void *ptr;				// sds
}

在这里插入图片描述

哈希-hash

      当存储的内容是对象的时候,Redis 字符串对象的很多功能使用Redis 哈希对象也可以实现。如缓存用户信息的时候,使用Redis哈希对象存储,简单直观,如果使用合理可以减少内存空间的使用。但也有其缺点,就是要要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。

列表-list

      存储一组有序的、可重复的数据。因为其有序性,它可以获取指定范围的元素列表、可以在O(1)的时间复杂度获取指定索引下标的元素等。
      通过使用lpush、rpush、lpop、rpop,可以实现栈和队列,也可以实现一个简易的消息队列。

集合-set

      无序并唯一的键值集合。它的存储顺序不会按照插入的先后顺序进行存储。
      集合无序,唯一,且支持交并差,可以用来做标签。
      通过 SPOP(随机移除并返回集合中一个或多个元素) 和 SRANDMEMBER(随机返回集合中一个或多个元素) 命令,可以实现一个抽奖系统。

有序集合-zset

      有序集合类型 (Sorted Set或ZSet) 相比于集合类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。
      有序集合键不可重复,分值可以重复;且有序集合中的元素可以排序。
      有序集合比较典型的使用场景就是排行榜系统例如学生成绩的排名、某视频(博客等)网站的用户点赞、播放排名、电商系统中商品的销量排名等。
      
      
      
      


版权声明:本文为weixin_42845682原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。