本文章主要整理redis的五种数据类型(string、list、hash、set、zset)的底层数据结构实现。
dictht是一个散列表结构,使用拉链法解决哈希冲突。
This is our hash table structure. Every dictionary has two of this as we implement incremental rehashing, for the old to new table.
typedef struct dictht {
dictEntry **table; //hash表数组
usigned long size; //hash表大小
usigned long sizemask; //hash 表大小掩码 为size - 1
usigned long used; //已使用的节点数量
}dictht;
typedef struct dictEntry {
void *key; //键
union { //值
void *val;
uint64_t u64;
int64_t s64;
double d;
}v;
struct dictEntry *next; //指向下一个节点的形成链表
} dictEntry;
Redis的字典dict中包含两个哈希表dictht,这是为了方便进行rehash操作。在扩容时,将其中一个dictht上的键值对rehash到另一个dictht上面,完成之后释放空间并交换两个dictht的角色。
typedef struct dict {
dictType *type; //类型特定函数
void *privdata; //私有数据
dictht ht[2]; //哈希表
long rehashidx; //rehash in progress rehash索引值
usigned long iterators; //当前运行的迭代器数目
} dict;
rehash 操作不是一次性完成,而是采用渐进方式,为了避免一次性执行过多的rehash操作给服务器带来过大的负担。
负载因子 = 哈希表已经保存的节点数量/哈希表大小。
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。具有如下性质:
1、由很多层结构组成;
2、每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点;
3、最底层的链表包含了所有的元素;
4、如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集);
5、链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;
跳跃表的实现
跳跃表由redis.h/zskiplistNode和redis.h/zkiplist两个结构定义。
//ZSETs ues a specialized viersion of skiplists
typedef struct zskiplistNode {
robj *obj; //成员对象
double score; //分值
struct zskiplistNode *backward; //后退指针
struct zskiplistLevel {
struct zskiplistNode *forward; //前进指针
usigned int span; //跨度
} level[];
} zskiplistNode;
节点的分值(score属性)是一个double类型的浮点数,跳跃表中的所有节点都按分值从小到大来排序。
typedef struct zskiplist {
struct zskiplistNode *header, *tail; //header指向跳跃表的表头节点,tail指向跳跃表的表尾节点
unsigned long length; //记录跳跃表的长度
int level; //记录跳跃表内,层数最大的那个节点的层数(表头不计算在内)
} zskiplist;
SDS (simple dynamic string)
简单动态字符串
struct sdsndr {
int len; //sds所保存的字符串长度
int free; //buf中没有使用的字节的数量
char buf[]; // 字节数组,用于保存字符串(大小为len+free+1)
}
链表的节点
typedef 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;
typedef struct insert {
/* 虽然 intset 结构将 contents 属性声明为 int8_t 类型的数组, 但实际上 contents 数组的真正类型取决于 encoding 属性的值:
如果 encoding 属性的值为 INTSET_ENC_INT16 , 那么 contents 就是一个 int16_t 类型的数组, 数组里的每个项都是一个 int16_t类型的整数值 (最小值为 -32,768 ,最大值为 32,767 )。
如果 encoding 属性的值为 INTSET_ENC_INT32 , 那么 contents 就是一个 int32_t 类型的数组, 数组里的每个项都是一个 int32_t类型的整数值 (最小值为 -2,147,483,648 ,最大值为 2,147,483,647 )。
如果 encoding 属性的值为 INTSET_ENC_INT64 , 那么 contents 就是一个 int64_t 类型的数组, 数组里的每个项都是一个 int64_t类型的整数值 (最小值为 -9,223,372,036,854,775,808 ,最大值为9,223,372,036,854,775,807 )。*/
uint32_t encoding; //编码方式
uint32_t length; //集合包含的元素数目
int8_t contents[]; //保存元素的数组
} intset;
压缩列表是Redis为节省内存而开发的顺序型数据结构,通常作为列表键和哈希键的底层实现之一。由一系列特殊编码的连续内存块组成的顺序型数据结构。
压缩表原理:将数据按照一定规则编码在一块连续的内存区域,目的是节省内存。
zlbytes | zltail | zllen | entry1 | entry2 | … | entryN | zlend |
---|
压缩列表的每个节点:
previous_entry_length | encoding | content |
---|