Redis数据结构与对象
字符串
Redis,自己构建了一种名为简单动态字符串的抽象类型SDS
数据结构
struct sdshdr{
// 记录buf数组中已经使用字节的数量
// 等于SDS所保存字符串的长度
int len;
// 记录buf数组中未使用的字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
}
比较C的字符串,SDS的优点:
- 常熟复杂度获取字符串长度
- 杜绝缓冲区溢出
- 减少修改字符串长度时所需的内存重分配次数
- 二进制安全
- 兼容C的字符串函数
链表
链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度
使用:链表键、发布与订阅、慢查询、监视器等
数据结构:
type struct listNode{
Struct listNode *prev;
Struct listNode *next;
void *value;
}listNode;
typedef struct list{
listNode *head;
listNode *tail;
unsigned long len;
/* 节点值复制函数 */
void *(*dup)(void *ptr);
/* 节点值释放函数 */
void (*free)(void *ptr);
/* 节点值对比函数 */
int (*match)(void *ptr,void *key);
}list;
特性:
1、双端
2、无环:head->prev = null tail->next = null
3、带表头和表尾指针
4、带链表长度计数器
5、多态 void* 保存节点值
字典
字典(符号表、关联数组、映射map),是一种用于保存键值对(key-value)的抽象数据结构
使用:redis的数据库底层实现就是字典,对数据库的增删改查就是建立在字典之上
数据结构:
哈希表定义
typedef struct dictht{
// 哈希数组
dictEntry **table;
// 哈希表大小
unsigned long size;
//哈希大小掩码,用于计算索引值,总是等于size-1
unsigned long sizemask;
//该哈希表已有节点的数量
unsigned long used;
}dictht;
其中table是一个数组,保存着dictEntry的键值对,其实也很好理解,想一想java的hashmap的实现
哈希表节点定义
typedef struct dictEntry{
// 键
void *key
// 值
union{
void *val;
uint64_t u64;
int64_t s64;
}
struct dictEntry *next;
}dictEntry;
union表示键值对的值可以多样化(指针、整数)
next指针也就表示了用链表解决哈希冲突
字典的定义
typedef struct dict{
// 类型特定函数
dictType *type;
// 私有数据
void *private;
// 哈希表
dictht ht[2];
// rehash索引
// 当rehash不在进行时,值为-1
int trehashidx;
}dict;
type指向了dictType结构的指针,包括以下几个函数:
- hashFunction 计算哈希值
- keyDup 复制键
- valDup 复制值
- keyCompare 对比键
- keyDestructor 销毁键
- valDestructor 销毁值
其中ht数组表示哈希表,字典只使用ht[0]哈希表,h[1]只会在rehash时使用
trehashidx记录了rehash目前的进度
如何计算哈希值?
hash = dict->type->hashFunction(key)
Redis使用的MurmurHash2算法来计算hash值
如何解决hash冲突?
链地址法,头插入,因为dictEntry没有指向尾结点的指针 ??? 那key相同怎么办
如何做rehash的?
渐进式rehash,不是一次性、集中式的完成的。考虑到如果哈希表里有大量键值对,采用分多次、渐进式地完成的。
在渐进式rehash期间,字典的删除、查找、更新等操作会在两个哈希表上进行,比如查找某key,会在ht[0]和ht[1]都找
跳跃表
跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找
Redis使用跳跃表:1、实现有序集合 2、在集群节点中用作内部数据结构
跳跃表节点
typedef struct zskiplistNode {
// 后退指针
struct zskiplistNode *backward;
// 分值
double score;
// 成员对象
robj *obj;
// 层
struct zkiplistLevel {
// 前进指针
struct zskiplistNode *forward;
// 跨度
unsigned int span;
} level[];
}zskiplistNode;
1、层
增加层能加快访问其他节点的速度
2、前进指针
指向表尾方向的前进指针
3、跨度
用来记录两个节点之间的距离
4、后退指针
表尾向表头方向访问节点
5、分值和成员
节点的分值(score属性)是一个double类型的浮点数,跳跃表中所有节点都按照分值从小到大排序
节点的成员(obj属性)是一个指针,它指向一个字符串对象,而字符串对象则保存着一个SDS值
跳跃表
通过一个zskiplist跳跃表结构来持有这些节点
typedef struct zskiplist {
// 表头节点和表尾节点
structz skiplistNode *header, *tail;
// 表中节点的数量
unsigned long length;
// 表中层数最大的节点的层数
int level;
}zskiplist;
- header、tail分别指向跳跃表的表头和表尾
- 跳跃表的层高都是1-32之间的随机数
- 在同一个跳跃表中,多个节点可以包含相同的分值,但是每个节点的成员对象必须是唯一的
- 跳跃表中的节点按照分值大小进行排序,分值相同时,按照成员对象的大小排序
- 插入快,无锁实现,不需要旋转保持平衡(对比红黑树)