Redis数据库中的每个键值对都是由对象组成,键总是一个字符串对象(string object),而值可以由字符串对象、列表对象(list object)、哈希对象(hash object)、集合对象(set object)和有序集合对象(sorted set object)这五种。下面分别介绍每一种。
一、简单动态字符串
1、定义
从Redis3.2开始,sds就有了5种类型,5种类型分别存放不同大小的字符串。其定义在sds.h文件中华,定义如下
typedef char *sds;
//sdshdr5在源码中没有用到
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 低三位为type, 高五位为字符串长度 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 已经使用长度 */
uint8_t alloc; /* 分配的字符串长度,除头部和结尾的空字符 */
unsigned char flags; /* 低三位为type, 高五位为字符串长度 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len;
uint16_t alloc;
unsigned char flags;
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len;
uint32_t alloc;
unsigned char flags;
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len;
uint64_t alloc;
unsigned char flags;
char buf[];
};
2、SDS和C语言字符串的区别和改进
2.1 获取字符串长度的复杂度是常数级的
C字符串获取长度的时候需要遍历整个字符串,此操作的复杂度为O(N)。而SDS记录了字符串的长度len。在设置和更新字符串的时候,自动更新此长度。
2.2 避免了缓冲区溢出
C字符串在拼接的时候,要保证拼接后的字符串已分配足够的长度,不然会造成缓冲区溢出。而SDS在修改字符串时先根据alloc判断空间是否满足修改所需,避免了缓冲区溢出。如果空间不够,SDS会先扩展空间。
2.3 减少修改字符串时带来的内存重分配次数
C字符串每次修改和删除都会进行一次内存重新分配。SDS通过空间预分配、惰性空间释放等措施减少内存分配。因为内存分配是非常耗时的过程。
2.4 二进制安全
C字符串必须符合某种编码,并且中间不能有空格,所以不能保存图片、音频等二进制的数据。Redis的buf数组是用来保存二进制数据的,所以没有这些限制。SDS是通过len来判断字符串是否结束,而不是通过空格。
二、链表
1、定义
链表在实际开发中的使用非常广泛,在Redis中也是,比如列表键、发布与订阅、慢查询、监视器,以及服务器本身。链表的定义在adlist.h中,定义如下:
//链表节点定义
typedef struct listNode {
//前节点
struct listNode *prev;
//后节点
struct listNode *next;
//节点值
void *value;
} listNode;
//链表迭代器
typedef struct listIter {
//链表下一节点
listNode *next;
//迭代方向
int direction;
} listIter;
//双端链表
typedef struct list {
//链表头节点
listNode *head;
//链表尾节点
listNode *tail;
//节点值复制函数
void *(*dup)(void *ptr);
//节点值释放函数
void (*free)(void *ptr);
//节点值比较函数
int (*match)(void *ptr, void *key);
//链表长度
unsigned long len;
} list;
2、Redis链表特性
双端:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。
无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。
带表头指针和表尾指针:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)。
带链表长度计数器:程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。
多态:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。
三、字典
字典,又称为符号表(symbol table)、关联数组(associative array)或映射(map),是一种用于保存键值对(key-value pair)的抽象数据结构。
字典在Redis中的应用相当广泛,比如Redis的数据库就是使用字典来作为底层实现的,对数据库的增、删、查、改操作也是构建在对字典的操作之上的。除了用来表示数据库之外,字典还是哈希键的底层实现之一,当一个哈希键包含的键值对比较多,又或者键值对中的元素都是比较长的字符串时,Redis就会使用字典作为哈希键的底层实现。
1、定义
Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。字典的定义在dict.h文件中,定义如下:
1.1、哈希表节点
//哈希表节点
typedef struct dictEntry {
//哈希键
void *key;
//哈希值
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
//指向下一个哈希表节点
struct dictEntry *next;
} dictEntry;
key属性保存着键值对中的键,而v属性则保存着键值对中的值,其中键值对的值可以是一个指针,或者是一个uint64_t整数,又或者是一个int64_t整数。next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一次,以此来解决键冲突(collision)的问题。
1.2 类型特定函数
//类型特定函数
typedef struct dictType {
//计算哈希值函数
uint64_t (*hashFunction)(const void *key);
//复制哈希键函数
void *(*keyDup)(void *privdata, const void *key);
//复制哈希值函数
void *(*valDup)(void *privdata, const void *obj);
//哈希键对比函数
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
//销毁哈希键函数
void (*keyDestructor)(void *privdata, void *key);
//销毁哈希值函数
void (*valDestructor)(void *privdata, void *obj);
} dictType;
每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
1.3 哈希表
//哈希表结构
typedef struct dictht {
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表掩码,总是等于size-1,用于计算索引值
unsigned long sizemask;
//哈希表已有节点的数量
unsigned long used;
} dictht;
1.4 字典
//Redis中的字典定义
typedef struct dict {
//类型特定函数,是dictType类型
dictType *type;
//私有数据
void *privdata;
//哈希表
dictht ht[2];
//rehash索引,当rehash不在进行时,值为-1
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
//当前迭代器的数量
unsigned long iterators; /* number of iterators currently running */
} dict;
type属性和privdata属性是针对不同类型的键值对,为创建多态字典而设置的:
type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
而privdata属性则保存了需要传给那些类型特定函数的可选参数。
1.5 字典的迭代器
//如果迭代器的数量设置为1是安全的,意味着可以调用dictAdd, dictFind等方法。否则
//是不安全的迭代器,只能调用dictNext()方法
typedef struct dictIterator {
dict *d;
long index;
int table, safe;
dictEntry *entry, *nextEntry;
//滥用fingerprint是非安全的迭代
long long fingerprint;
} dictIterator;
哈希算法