【面试题】redis的底层数据结构

1. redis底层用了什么数据结构

该命令是用来显示那五大数据类型的底层数据结构。

OBJECT ENCODING   key 

1.1 示例一:String类型

在这里插入图片描述

1.2 示例二: List类型

在这里插入图片描述

这里我们就不做过多的演示了,那么上次出现的 embstr 以及 int 还有 quicklist 是什么数据结构呢?下面我们就来介绍Redis中几种主要的数据结构。

2. 五种数据类型底层用了什么数据结构

2.1 String(SDS)

字符串的长度不能超过512M。
字符串对象的编码可以是int,raw或者embstr。
int 编码:保存的是可以用 long 类型表示的整数值。
embstr 编码:保存长度小于44字节的字符串。
raw 编码:保存长度大于44字节的字符串。

int 编码是用来保存整数值,raw编码是用来保存长字符串,而embstr是用来保存短字符串。其实 embstr 编码是专门用来保存短字符串的一种优化编码,raw 和 embstr 的区别:
在这里插入图片描述
在这里插入图片描述
embstr与raw都使用redisObject和sds保存数据,区别在于,embstr的使用只分配一次内存空间(因此redisObject和sds是连续的),而raw需要分配两次内存空间(分别为redisObject和sds分配空间)。

ps:Redis中对于浮点数类型也是作为字符串保存的,在需要的时候再将其转换成浮点数类型。

2.2 List(linkedlist、ziplist)

列表对象的编码可以是 ziplist(压缩列表) 和 linkedlist(双端链表)。

比如我们执行以下命令,创建一个 key = ‘numbers’,value = ‘1 three 5’ 的三个值的列表。

rpush numbers 1 "three" 5

ziplist 编码表示如下:
在这里插入图片描述
 linkedlist表示如下:
 在这里插入图片描述
编码转换
当同时满足下面两个条件时,使用ziplist(压缩列表)编码:

  1. 列表保存元素个数小于512个
  2. 每个元素长度小于64字节
    不能满足这两个条件的时候使用 linkedlist 编码。
    上面两个条件可以在redis.conf 配置文件中的 list-max-ziplist-value选项和 list-max-ziplist-entries 选项进行配置。

2.3 hash(hashtable、ziplist )

哈希对象的编码可以是 ziplist 或者 hashtable。

比如执行以下命令:

hset profile name "Tom"
hset profile age 25
hset profile career "Programmer"

如果使用ziplist,profile 存储如下:
在这里插入图片描述
当使用 hashtable 编码时,上面命令存储如下:
在这里插入图片描述

编码转换
使用ziplist(压缩列表)编码必须满足下面两个条件,不能满足这两个条件的时候使用 hashtable 编码。

  1. 列表保存元素个数小于512个
  2. 每个元素长度小于64字节

2.4 Set(intset、hashtable)

set的底层存储intset和hashtable是存在编码转换的,使用intset存储必须满足下面两个条件,否则使用hashtable,条件如下:
结合对象保存的所有元素都是整数值,且个数不超512个。

intset我们可以理解为数组,hashtable就是普通的哈希表(key为set的值,value为null)

2.5 Zset(ziplist 、skiplist)

有序集合的编码可以是 ziplist 或者 skiplist。

ZADD price 8.5 apple 5.0 banana 6.0 cherry

每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。
在这里插入图片描述
编码转换
当有序集合对象同时满足以下两个条件时,对象使用 ziplist 编码:

  1. 保存的元素数量小于128;
  2. 保存的所有元素长度都小于64字节。
    不能满足上面两个条件的使用 skiplist 编码。

总结
对于string 数据类型,因为string 类型是二进制安全的,可以用来存放图片,视频等内容,另外由于Redis的高性能读写功能,而string类型的value也可以是数字,可以用作计数器(INCR,DECR),比如分布式环境中统计系统的在线人数,秒杀等。
对于 hash 数据类型,value 存放的是键值对,比如可以做单点登录存放用户信息。
对于 list 数据类型,可以实现简单的消息队列,另外可以利用lrange命令,做基于redis的分页功能
对于 set 数据类型,由于底层是字典实现的,查找元素特别快,另外set 数据类型不允许重复,利用这两个特性我们可以进行全局去重,比如在用户注册模块,判断用户名是否注册;另外就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
对于 zset 数据类型,有序的集合,可以做范围查找,排行榜应用,取 TOP N 操作等。

3. 底层结构详解

3.1 SDS( 简单动态字符串 )

Redis 是用 C 语言写的,但是对于Redis的字符串,却不是 C 语言中的字符串,它是自己构建了一种名为 简单动态字符串(simple dynamic string,SDS)的抽象类型,并将 SDS 作为 Redis的默认字符串表示。

3.1.1 SDS结构

struct sdshdr{
     //记录buf数组中已使用字节的数量
     //等于 SDS 保存字符串的长度
     int len;
     //记录 buf 数组中未使用字节的数量
     int free;
     //字节数组,用于保存字符串
     char buf[];
}

在这里插入图片描述

3.2.2 为什么不使用C语言字符串实现,而是使用 SDS呢?这样实现有什么好处?

上面的定义相对于 C 语言对于字符串的定义,多出了 len 属性以及 free 属性。
常数复杂度获取字符串长度
由于 len 属性的存在,我们获取 SDS 字符串的长度只需要读取 len 属性,时间复杂度为 O(1)。而对于 C 语言,获取字符串的长度通常是经过遍历计数来实现的,时间复杂度为 O(n)。通过 strlen key 命令可以获取 key 的字符串长度。
3.2.2.2 减少修改字符串的内存重新分配次数
 C语言由于不记录字符串的长度,所以如果要修改字符串,必须要重新分配内存(先释放再申请),因为如果没有重新分配,字符串长度增大时会造成内存缓冲区溢出,字符串长度减小时会造成内存泄露。
 而对于SDS,由于len属性和free属性的存在,对于修改字符串SDS实现了空间预分配和惰性空间释放两种策略:
  1. 空间预分配:对字符串进行空间扩展的时候,扩展的内存比实际需要的多,这样可以减少连续执行字符串增长操作所需的内存重分配次数。
  2. 惰性空间释放:对字符串进行缩短操作时,程序不立即使用内存重新分配来回收缩短后多余的字节,而是使用 free 属性将这些字节的数量记录下来,等待后续使用。(当然SDS也提供了相应的API,当我们有需要时,也可以手动释放这些未使用的空间。)
在这里插入图片描述

3.2 链表(双向链表)

链表是一种常用的数据结构,C 语言内部是没有内置这种数据结构的实现,所以Redis自己构建了链表的实现。

3.2.1 链表定义

typedef  struct listNode{
       //前置节点
       struct listNode *prev;
       //后置节点
       struct listNode *next;
       //节点的值
       void *value;  
}listNode

通过多个 listNode 结构就可以组成链表,这是一个双向链表,Redis还提供了操作链表的数据结构:

typedef struct list{
     //表头节点
     listNode *head;
     //表尾节点
     listNode *tail;
     //链表所包含的节点数量
     unsigned long len;
     //节点值复制函数
     void (*free) (void *ptr);
     //节点值释放函数
     void (*free) (void *ptr);
     //节点值对比函数
     int (*match) (void *ptr,void *key);
}list;

在这里插入图片描述

3.2.2 Redis链表特性

  1. 双端:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为O(1)。
  2. 无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL 结束。
  3. 带链表长度计数器:通过 len 属性获取链表长度的时间复杂度为 O(1)。
  4. 多态:链表节点使用 void* 指针来保存节点值,可以保存各种不同类型的值。

3.3 跳跃表

3.3.1 结构

在这里插入图片描述
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。具有如下性质:

  1. 由很多层结构组成;
  2. 每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点;
  3. 最底层的链表包含了所有的元素;
  4. 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集);
  5. 链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;

3.3.2 查询

搜索
从最高层的链表节点开始,如果比当前节点要大和比当前层的下一个节点要小,那么则往下找,也就是和当前层的下一层的节点的下一个节点进行比较,以此类推,一直找到最底层的最后一个节点,如果找到则返回,反之则返回空。
插入
首先确定插入的层数,有一种方法是假设抛一枚硬币,如果是正面就累加,直到遇见反面为止,最后记录正面的次数作为插入的层数。当确定插入的层数k后,则需要将新元素插入到从底层到k层。
删除
在各个层中找到包含指定值的节点,然后将节点从链表中删除即可,如果删除以后只剩下头尾两个节点,则删除这一层。

4.4. 字典

用于保存键值对的抽象数据结构。字典中的每一个键 key 都是唯一的,通过 key 可以对值来进行查找或修改。
使用哈希表作为底层实现。

4.4.1 哈希表结构定义

typedef struct dictht{
     //哈希表数组
     dictEntry **table;
     //哈希表大小
     unsigned long size;
     //哈希表大小掩码,用于计算索引值
     //总是等于 size-1
     unsigned long sizemask;
     //该哈希表已有节点的数量
     unsigned long used;
}dictht

哈希表是由数组 table 组成,table 中每个元素都是指向 dict.h/dictEntry 结构,dictEntry 结构定义如下:

typedef struct dictEntry{
     //键
     void *key;
     //值
     union{
          void *val;
          uint64_tu64;
          int64_ts64;
     }v;
 
     //指向下一个哈希表节点,形成链表
     struct dictEntry *next;
}dictEntry

4.4.2 哈希表最大的问题是存在哈希冲突,如何解决哈希冲

有开放地址法和链地址法。这里采用的便是链地址法,通过next这个指针可以将多个哈希值相同的键值对连接在一起,用来解决哈希冲突。
在这里插入图片描述

4.5 压缩列表

压缩列表并不是对数据利用某种算法进行压缩,而是将数据按照一定规则编码在一块连续的内存区域,目的是节省内存。
在这里插入图片描述在这里插入图片描述

  1. previous_entry_ength:记录压缩列表前一个字节的长度。当前节点位置减去上一个节点的长度即得到上一个节点的起始位置,压缩列表可以从尾部向头部遍历。这么做很有效地减少了内存的浪费。
  2. encoding:节点的encoding保存的是节点的content的内容类型以及长度,encoding类型一共有两种,一种字节数组一种是整数,encoding区域长度为1字节、2字节或者5字节长。
  3. content:content区域用于保存节点的内容,节点内容类型和长度由encoding决定。

4.6 intset

intset的结构

typedf struct inset{
    uint32_t encoding;//编码方式 有三种 默认 INSET_ENC_INT16
    uint32_t length;//集合元素个数
    int8_t contents[];//实际存储元素的数组 
                      //元素类型并不一定是ini8_t类型,柔性数组不占intset结构体大小,并且数组中的元
                      //素从小到大排列
}inset;

编码格式encoding:共有三种,INTSET_ENC_INT16、INSET_ENC_INT32和INSET_ENC_INT64三种,分别对应不同的范围。Redis为了尽可能地节省内存,会根据插入数据的大小选择不一样的类型来进行存储。
元素数量length:记录了保存数据的数组contents中共有多少个元素,这样获取个数的时间复杂度就是O(1)。
数组contents:真正存储数据的地方,数组是按照从小到大有序排列的,并且不包含任何重复项。
在这里插入图片描述
优点:根据存入的数据大小选择合适的编码方式,且只在必要的时候进行升级操作,节省内存
缺点:升级过程耗费系统资源,还有就是不支持降级,一旦升级就不可以降级
在这里插入图片描述

来源:https://www.cnblogs.com/ysocean/p/9080942.html
intset结构:https://blog.csdn.net/ymb615ymb/article/details/123371540

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
面试回答中,可以提到以下几种Redis的基本数据结构和一些其他的数据结构。 1. 字符串(String): Redis的最基本的数据结构,可以存储字符串、整数或者浮点数。 2. 哈希(Hash): Redis的哈希是一个键值对的集合,每个键对应一个值。哈希表的底层实现使用了字典和压缩列表,可以实现O(1)的查询和设置速度。 3. 列表(List): Redis的列表是一系列按照插入顺序排序的元素集合,可以在头部或者尾部插入元素,支持按索引访问和范围访问等操作。 4. 集合(Set): Redis的集合是一个无序且唯一的元素集合,支持添加、删除和判断元素是否存在等操作,还可以进行交集、并集和差集等常见操作。 5. 有序集合(ZSet): Redis的有序集合是一个有序的元素集合,每个元素都有一个分值,可以根据分值进行排序,支持按分值范围查询和按成员访问等操作。 此外,还可以提到一些其他的Redis数据结构: - HyperLogLog: 用于估计一个集合的基数,即不重复元素的数量。 - Geo: 用于地理位置信息的存储和查询。 - Pub/Sub: 用于发布和订阅消息的机制。 - Redis Module: 通过扩展模块的方式,可以添加自定义的数据结构和功能。 - BloomFilter: 用于判断一个元素是否存在于一个集合中的概率型数据结构。 - RedisSearch: 用于全文搜索和索引的高性能搜索引擎。 - Redis-ML: 用于机器学习模型的存储和使用。 - 哈希槽的概念: Redis的哈希槽是一种数据分片的方式,将数据分散存储在不同的槽位上,以提高集群的负载均衡和扩展性。 - 扩容原理: Redis的扩容原理是通过增加或减少哈希槽的数量来实现的,当需要扩容时,会将一部分的哈希槽迁移到新的节点上。 综上所述,这些是Redis常用的数据结构和一些其他的数据结构,可以在面试回答中提到它们的特点和用途。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [面试官问 Redis数据结构的时候怎么答能加分?](https://blog.csdn.net/qq_45881167/article/details/128424300)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [2022年Redis最新面试题第2篇 - Redis数据结构](https://blog.csdn.net/uuqaz/article/details/125446058)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值