16.1 数据结构
-
SDS 动态字符串
-
双向链表
-
压缩列表 ziplist
-
哈希表 hashtable
-
跳表 skiplist
-
整数集合 intset
-
快速列表 quicklist
-
紧凑列表 listpack
16.2 KV 键值对
-
redis 是 key-value 存储系统
-
key 一般都是 String 类型的字符串对象
-
vlaue 类型则为 redis 对象(redisObject)
-
value 可以是字符串对象,也可以是集合数据类型的对象,比如 List 对象、Hash 对象、Set 对象和 Zset 对象
-
16.2.1 10 大类型
-
传统的 5 大类型
-
String
-
List
-
Hash
-
Set
-
ZSet
-
-
新 5 大类型
-
bitmap,实质 String
-
hyperLogLog,实质 String
-
GEO,实质 Zset
-
Stream,实质 Stream
-
BITFIELD,看具体 key
-
16.2.2 上帝视角
-
Redis 定义了 redisObjec 结构体,来表示 string、hash、list、set、zset 等数据类型
-
redis 中每个对象都是一个 redisObject 结构
-
字典、KV 是什么(重点)
-
每个键值对都会有一个 dictEntry
-
源码:dict.h
-
-
这些键值对是如何保存进 redis 并进行读取操作,O(1)复杂度
-
redisObject+redis 数据类型+Redis 所有编码方式(底层实现)三者之间的关系
16.3 底层 C 语言源码分析
16.3.1 数据结构
-
SDS 动态字符串
-
双向链表
-
压缩列表 ziplist
-
哈希表 hashtable
-
跳表 skiplist
-
整数集合 intset
-
快速列表 quicklist
-
紧凑列表 listpack
-
redis6.0.5
-
redis7
16.3.2 经典 5 大数据结构解析
…(省略)
16.4 skiplist 跳表
16.4.1 为什么引出跳表
对于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。
这样查找效率就会很低,时间复杂度会很高 O(N)
16.4.2 痛点
解决方法:升维,也叫空间换时间。
- 优化 1
从这个例子里,我们看出,加来一层索引之后,查找一个结点需要遍历的结点个数减少了,也就是说查找效率提高了。
- 优化 2
画了一个包含 64 个结点的链表,按照前面讲的这种思路,建立了五级索引
16.4.3 是什么
-
跳表是可以实现二分查找的有序链表
-
skiplist 是一种以空间换取时间的结构。
-
由于链表,无法进行二分查找,因此借鉴数据库索引的思想,提取出链表中关键节点(索引),先在关键节点上查找,再进入下层链表查找,提取多层关键节点,就形成了跳跃表
-
由于索引也要占据一定空间的,所以,索引添加的越多,空间占用的越多
-
总结来讲 跳表 = 链表 + 多级索引
16.4.4 复杂度
-
时间复杂度
-
跳表查询的时间复杂度分析,如果链表里有 N 个结点,会有多少级索引呢?
-
按照我们前面讲的,两两取首。每两个结点会抽出一个结点作为上一级索引的结点,以此估算:
-
第一级索引的结点个数大约就是 n/2,
-
第二级索引的结点个数大约就是 n/4,
-
第三级索引的结点个数大约就是 n/8,依次类推…
-
也就是说,第 k 级索引的结点个数是第 k-1 级索引的结点个数的 1/2,那第 k 级索引结点的个数就是 n/(2^k)
-
-
空间复杂度
16.4.5 优缺点
-
优点
- 跳表是一个最典型的空间换时间解决方案,而且只有在数据量较大的情况下才能体现出来优势。而且是读多血少的情况下才能使用,所以他的使用范围应该还是比较有限的
-
缺点
-
维护成本相对要高
-
在单链表中,一旦定位好要插入的位置,插入节点的时间复杂度是很低的,就是 O(1)
-
但是,新增或者删除是需要把所有索引都更新一遍,为了保证原始链表中数据的有序性,我们需要先找到要动作的位置,这个查找操作就会比较耗时,最后在新增和删除的过程中的更新,时间复杂度也是 O(log n)
-