1-8章 数据结构与对象
-
redis中没有使用c语言的字符串,而是用到了简单动态字符串(SDS),set name "hello"表示的意思是:键值对的键是一个字符串对象,底层实现是一个保存字符串name的SDS。
-
SDS的作用:处理用来保存字符串值之外,还用作缓冲区(AOF模块中的AOF缓冲区、客户端状态中的输入缓冲区)
-
SDS定义
-
buf中最后一个'\0'并不计算在len中
-
-
为什么使用SDS形式的字符串而不是直接用C字符串?
-
提高了性能:C字符串获取长度的时间复杂度为O(N),而SDS是O(1)
-
杜绝缓冲区溢出:因为C字符串并不会计算自身长度,假如拼接字符串的时候就有可能造成溢出,但是SDS在执行拼接操作的API时会先检查空间是否足够,不够的话先扩容然后拼接
-
减少修改字符串时带来的内存重分配次数:C字符串增加或者减少字符串操作时都需要进行内存重分配,但是SDS通过未使用空间实现了空间预分配和惰性空间释放两种优化策略
-
空间预分配优化字符串增长操作:不仅会分配必要的空间还会分配额外的空间
-
惰性空间优化字符串缩短操作:空间不会被回收,而是加入free中
-
-
二进制安全:SDS API会以处理二进制的方式来处理SDS中存放的buf中的数据,redis不仅可以存放文本,还可以存放任意二进制的数据
-
兼容部分C字符串函数:SDS会遵循C字符串以空字符结尾的惯例,这样可以使用C函数库中的函数
-
3、链表
-
用到链表的地方:列表键、发布与订阅、慢查询、监视器
4、字典
-
redis的数据库的底层就是使用字典实现的,对数据库的增删改查也是建立在操作字典之上。字典还是哈希键的底层实现之一
-
字典使用哈希表作为底层实现
-
数据结构表示:
-
typedef struct dictht{
dictEntry ** table; //哈希表数组
undigned long size; //哈希表大小
unsigned long sizemask; //哈希表大小掩码,用于计算索引值,size-1
unsigned long used; //已有的结点的数量
}dictht;
typedef struct dictEntry{
void *key;
union{
void *val;
uint64_tu64;
int64_ts64;
}v;
struct dictEntry *next;
}dictEntry; -
redis中的字典如下表示:
-
typedef struct dict{
dictType *type; //类型特定函数
void *privdata; //私有数据
dictht ht[2]; //哈希表
int rehashidx //rehash索引,记录了rehash目前的进度
}dict;-
一般情况下字典只使用ht[0]作为哈希表,ht[1]只会对ht[0]进行rehash时使用
-
-
当要插入键值对的时候计算出哈希值和索引值,redis使用Murmurhash2算法计算键的哈希值
-
redis使用链地址法解决哈希冲突。为了速度考虑,冲突的结点插入到链表的表头位置
-
为了让哈希表的负载因子维持在合理的范围之内,程序需要对哈希表进行rehash操作,过程如图所示:
-
渐进式rehash:rehash操作并不是一次、集中式完成的,而是分多次渐进式完成的。因为当ht[0]数据很多的时候,一次性的rehash会造成程序的卡顿。过程为:当对ht[0]中的键值对进行添加、删除、查找、更新操作的时候,不仅完成此操作还要把值移动到ht[1],但是添加操作直接把值加到ht[1]
5、跳跃表
-
使用场景:redis中的有序集合键底层、集群结点中用作内部数据结构
-
跳跃表中的概念:层、后退指针、跨度、前进指针、分值、成员对象
-
跨度:实际用来计算排位的,在查找某个节点的过程中,把沿途的所有访问的层累积起来就是排位
-
成员对象:实际是一个值,它指向字符串对象,底层实现是SDS
-
-
每个跳跃表的层高都是1-32之间的随机数
6、整数集合
-
当一个集合中只包含整数元素,并且元素的个数不多时,redis会用整数集合作为集合键的底层实现,保证集合中的元素不重复
- 整数集合底层用数组来实现。结构体中会有encoding表示编码,contents[]表示真正的数组。
- 当插入的元素和现存的数据类型不一样时会先进行升级(分配空间、复制元素、插入),不支持降级操作
- 升级可以:提升灵活性和节约内存
7、压缩列表
-
redis中的列表键和哈希键的底层实现之一,当列表项是小的整数值或者短的字符串时
-
压缩列表是由一系列特殊编码的连续的内存块组成的顺序型数据结构
- zlbytes:记录整个压缩列表占用的内存数
- zltail:记录表尾节点到压缩列表的起始地址的字节数
- zlen:包含的节点数量
- zlend:特殊值oxff
-
其中压缩列表节点entry包含
-
previous_entry_length:前一个节点的长度,可以从表尾向表头遍历,大小为1或者5
-
encoding:记录了节点的content属性所保存数据的类型以及长度
-
content:负责保存节点的值,可以是一个字节数组或者整数
-
-
连锁更新情况
-
空间扩展:比如e1 e2 e3中的长度位于250-253字节之间,所有previous_entry_length长度为1字节即可,但在e1前面加入一个255字节的数据,那么e1的previous_entry_length就要变成5字节,这时e1就会大于254,所以e2中的previous_entry_length也要变化,所以引发了一系列的改变
-
同样删除节点也会引发连锁更新
-
8、对象
- 字符串
- set msg "hello"
- list(压缩列表)
- rpush name "jiin" "yanjao" "jintian"
- 哈希对象(压缩列表、哈希表)
- hset class1 renshu 56 zuigao 99
- 集合 set(整数集合、哈希表)
- sadd number 1 3 5
- 有序集合(跳跃表)
- zadd price apple 5 banada 10 cherry 6