redis数据结构

数据结构与数据类型

众所周知,redis有五种数据类型:字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(sorted set),这五种数据类型底层又有不同的数据结构实现

一、SDS(简单动态字符串)

redis字符串数据类型底层存储结构

struct sdshdr {
    //记录buf数组中已使用字节的数量
    //等于SDS所保存字符串的长度
    int len;
    //记录buf数组中未使用字节的数量
    int free;
    //字节数组,用于保存字符串
    char buf[];
};

为什么redis不采用C字符串而选择SDS的结构来存储字符串类型
(1)C字符串取长度需要遍历整个字符串,而SDS可以直接访问len属性获取字符串长度
(2)减少内存重分配次数,通过free属性,可预置未使用内存空间,当对字符串操作时,可以减少内存重分配次数
(3)二进制安全,C语言字符串以空字符结尾,不能存储类似以空字符分割的二进制数据串等,而SDS api则是以二进制的形式处理buf数组的数据,保证二进制安全

二、链表

链表是列表数据类型的底层实现之一,当一个列表包含了数据比较多的元素,或者列表中包含的元素都是比较长的字符串时,列表就会采用链表来实现

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;

(1)redis链表为双向链表
(2)list结构保存了链表的头尾指针以及链表的节点数量,获取链表长度的时间复杂度为O(1)
(3)链表使用void来保存节点值,使得链表可以保存各种不同数据类型的值

三、字典

字典是一种用于保存键值对的抽象数据结构,redis所使用的C语言没有内置类似java map的这种数据结构,因为其构建了自己的字典实现
redis的数据库就是基于字典来实现的,其增删改查操作就是建立在字典的基础上
字典也是哈希数据类型的底层实现之一,当一个哈希包含的键值对比较多,或者键值对中的元素都是比较长的字符串时,就会采用字典的数据结构

typedef struct dict {
    //类型特定函数
    dictType *type;
    //私有数据
    void *privdata;
    //哈希表
    dictht ht[2];
    // rehash索引
    //当rehash不在进行时,值为-1
    in trehashidx; /* rehashing not in progress if rehashidx == -1 */
} dict;

(1)hash冲突:当发生hash冲突时,多个hash表节点用next指针形成一个单向链表
(2)再hash:随着键值对的数量越来越多,出现hash冲突的概率会增大,字典的读取效率会降低,因此需要进行扩容再hash
hash表的下一次容量大小会是 >= 当前已使用容量*2的第一个2的n次幂的值,例如当前hash表已使用3,那么下一次扩容容量大小为8
(3)负载因子计算:
hash表已保存的键值对的数量 / hash表的size
负载因子大于1或者小于0.1时进行扩容和缩容操作

四、跳表(skiplist)

跳表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的
跳表是有序集合数据类型的底层实现之一,当有序集合的元素数量比较多,或者是有序集合元素的成员是比较长的字符串时,就会使用跳表作为底层实现
跳表在原来的有序链表的基础上,加上了多级索引,可以支持快速的插入、删除和查找操作

typedef struct zskiplistNode {
    //层
    struct zskiplistLevel {
        //前进指针
        struct zskiplistNode *forward;
        //跨度
        unsigned int span;
    } level[];
    //后退指针
    struct zskiplistNode *backward;
    //分值
    double score;
    //成员对象
    robj *obj;
} zskiplistNode;

redis数据结构_第1张图片

(1)跳表的level是随机生成的,介于1-32之间
(2)跳表以空间换时间,插入、查找和删除的时间复杂度均是O(logn)
(3)有序集合采用hash + 跳表结构实现,hash用于存储value到score的映射,可以在O(1)的时间内找到value对应的score
skiplist按从小到大的顺序存储分数,skiplist每个节点的值都是[value, score]对

五、整数集合

整数集合是redis用于保存整数值的集合抽象数据结构,它可以保存int16,int32,int64的整数值
整数集合是集合数据类型的底层实现之一

typedef struct intset {
  //整数集合中元素的编码方式
  uint32_t encoding;
  //整数集合的元素数量,即contents数组的长度
  uint32_t length;
  //保存元素的数组,数组中的元素从大到小有序排列,不会出现重复元素
  //虽然contents数组被声明为int8_t类型,但数组中的元素的类型由encoding的值决定
  //encoding的取值为int16_t、int32_t和int64_t中一种
  int8_t contents[];
}intset;

六、压缩列表

压缩列表是列表和hash数据类型底层实现之一,当一个列表的元素个数很少且元素size也较小时,就会采用压缩列表存储;当一个hash键值对数量很少且键值size都较小时,就会采用压缩列表存储
在这里插入图片描述
在这里插入图片描述
(1)压缩列表是为了节约内存而开发的一种顺序型数据结构
(2)压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值
(3)添加新节点到压缩列表,或者从压缩列表中删除节点,可能会引发连锁更新操作,但这种操作出现的几率并不高

六、总结

五大数据类型底层采用的数据结构汇总:
(1)string字符串:采用SDS存储
(2)hash哈希: 当hash键值对数量很少且键值size都较小时,就会采用压缩列表存储;当hash类型无法满足ziplist条件时,采用字典存储
(3)list列表:当一个列表的元素个数很少且元素size也较小时,就会采用压缩列表存储;当列表不满足ziplist条件时,就采用链表存储
(4)set集合:当集合中的元素都是整数且元素个数小于512时,就采用整数集合存储;当不满足整数集合条件时,采用字典作为底层实现
(5)zset有序集合:当有序集合的元素个数小于128个,且元素的size小于64字节时,就采用压缩列表存储;当ziplist不满足条件时,就采用跳表存储

你可能感兴趣的:(redis)