value 的数据类型和数据结构的对应关系
String(字符串)、List(列表)、Hash(哈希)、Set(集合)和 Sorted Set(有序集合)这些只是 Redis 键值对中值的数据类型,也就是数据的保存形式。它们的底层数据结构一共有 6 种,分别是:简单动态字符串、双向链表、压缩列表、哈希表、跳表和整数数组。
数据类型 和 底层数据结构 的对应关系如下图所示:
String 类型的底层实现只有一种数据结构,也就是简单动态字符串。
List、Hash、Set 和 Sorted Set 这四种数据类型,都有两种底层实现结构。通常情况下,我们会把这四种类型称为集合类型,它们的特点是一个键对应了一个集合的数据。
不同操作的时间复杂度
和 String 类型不同,一个集合类型的值,第一步是通过全局哈希表找到对应的哈希桶位置,第二步是在集合中再增删改查。在集合中,操作的时间复杂度的影响因素有:
- 集合的底层数据结构;
- 操作本身的执行特点。
不同的集合类型有不同的底层数据结构,不同的数据结构适合不同的操作场景。
按照操作的作用范围,可以将操作分为:单元素操作(多元素操作)范围操作、统计操作、例外情况
集合常见操作的时间复杂度口诀:
- 单元素操作是基础;
- 范围操作非常耗时;
- 统计操作通常高效;
- 例外情况只有几个。
单元素操作
单元素操作 是指:对集合中单个元素的增删改查操作。
例如,Hash 类型的 HGET、HSET 和 HDEL,Set 类型的 SADD、SREM、SRANDMEMBER 等。
单元素操作的时间复杂度由集合采用的数据结构决定,例如,HGET、HSET 和 HDEL 是对哈希表做操作,所以它们的复杂度都是 O(1);Set 类型使用哈希表作为数据结构时,它的 SADD、SREM、SRANDMEMBER 复杂度也是 O(1)。
多元素操作
这里,有个地方你需要注意一下,集合类型支持同时对多个元素进行增删改查,例如 Hash 类型的 HMGET 和 HMSET,Set 类型的 SADD 也支持同时增加多个元素。
此时,这些操作的复杂度,就是由单元素操作的时间复杂度和元素个数决定的。例如,HMSET 增加 M 个元素时,复杂度就从 O(1) 变成 O(M) 了。
范围操作
范围操作 是指:对集合中的遍历操作,可以返回集合中的所有数据或者索引区间内的范围数据。
比如 Hash 类型的 HGETALL 和 Set 类型的 SMEMBERS,或者返回索引区间内的范围数据,比如 List 类型的 LRANGE 和 ZSet 类型的 ZRANGE。
范围操作的时间复杂度一般是 O(N),比较耗时,我们应该尽量避免。
不过,Redis 从 2.8 版本开始提供了 SCAN 系列操作(包括 HSCAN,SSCAN 和 ZSCAN),这类操作实现了渐进式遍历,每次只返回有限数量的数据。这样一来,相比于 HGETALL、SMEMBERS 这类操作来说,就避免了一次性返回所有元素而导致的线程长时间阻塞。
统计操作
统计操作 是指:统计集合中所有元素的个数
例如 LLEN 和 SCARD。
统计操作的时间复杂度只有 O(1),这是因为当集合类型采用压缩列表、双向链表、整数数组这些数据结构时,这些结构中专门记录了元素的个数统计,因此可以快速实现个数统计操作。
例外情况
例外情况 是指:某些数据结构的特殊记录。
例如压缩列表和双向链表都会记录表头和表尾的偏移量。这样一来,对于 List 类型的 LPOP、RPOP、LPUSH、RPUSH 这四个操作来说,它们是在列表的头尾增删元素,这就可以通过偏移量直接定位,所以它们的时间复杂度只有 O(1),可以快速实现相关操作。
集合类型是否允许重复
List 允许重复
Set 不允许重复
Sorted Set 不允许重复:如果重复设置,则会覆盖
Hash 的 key 不允许重复:
- 如果使用 hset 命令设置,则会覆盖
- 如果使用 hsetnx 命令设置,则只有在 key 不存在时才会设置
List 和 Set 有什么区别
- 是否允许重复:List 允许重复;Set 不允许重复
- 底层数据结构不同:List 是双向链表、压缩列表,Set 是整数数组、哈希表
我的疑问
Set 使用哈希表,那哈希表的 value 值是什么
Set 使用整数数组,怎么存储