Redis(一)数据结构解析

本文深入解析Redis中的数据结构,包括SDS、链表、字典、跳跃表、整数集合、压缩列表和对象等,详细介绍了它们的实现原理、特点和优化策略,如空间预分配、惰性空间释放等。
摘要由CSDN通过智能技术生成

目录

1. SDS 的定义

2. 动态字符串

常数复杂度获取字符串长度

杜绝缓存区溢出

SDS 两种优化策略

空间预分配

惰性空间释放

3. 链表和链表节点

每个链表节点用listNode结构表示

链表结构用list结构表示

Redis的链表实现的特性:

双端

无环

带表头指针和表尾指针

带链表长度计数器

多态

4. 字典的实现

4.1 字典结构

哈希表

 哈希表节点

 字典

 4.2 哈希算法

4.3 哈希冲突

4.4 rehash

 执行rehash步骤:

 哈希表的扩展和收缩:

  负载因子 = 哈希表已保存的节点数量/哈希表大小

  渐进式rehash

5. 跳跃表

  跳跃表节点             

  level数组    

    分值和成员

 跳跃表结构

 6. 整数集合

 整数集合结构

  扩容

7. 压缩列表

压缩列表的构成

压缩列表节点的构成

previou_entry_length

 encoding

 content

连锁更新

8. 对象

对象的类型和编码

类型

不同类型和编码的对象

 字符串对象

int

raw

embstr

 字符串命令

 列表对象

ziplist

 linkedlist​

 列表命令

 哈希对象

ziplist

hashtable

哈希命令

 集合对象

intset

 hashtable

集合命令

 有序集合对象

ziplist

 skiplist

 有序集合命令

类型检查和命令多态        

内存回收

对象共享

对象的空转时长


前言

本文基于 黄建宏-《Redis设计与实现》总结。第一部分为Redis 数据结构解析

1. SDS 的定义

struct sdshdr{
    int len; //buf数组中已用字节长度
    int free;//buf数组中未用字节长度
    char buf[]//buf数组
}

2. 动态字符串

常数复杂度获取字符串长度

        O(1),相较于C语言的O(n)

杜绝缓存区溢出

        字符串拼接时,SDS API先检查SDS空间,空间不足则扩展空间,再拼接

SDS 两种优化策略

空间预分配

        对SDS进行空间扩展时,如果修改后的SDS的长度将小于1MB,则分配和len属性同样大小的未使用空间,即SDS的len属性和free属性相同

如果修改后的SDS的长度将大于1MB,则分配1MB的未使用空间

通过空间预分配策略,Redis可以减少连续执行字符串增长操作所需的内存重分配次数

惰性空间释放

        当SDS API需要缩短SDS保存的字符串时,程序不立即使用内存重分配来回收多出来的字节,而是使用free属性记录数量

C字符串和SDS之间的区别
C字符串 SDS 备注
获取字符串长度的复杂度为O(N) 获取字符串长度的复杂度为O(1) 因为SDS有属性len,而C字符串需要遍历直至找到空字符串
API是不安全的,可能会造成缓冲区溢出 API是安全的,不会造成缓冲区溢出 SDS API先检查空间,空间不足扩展后在拼接
修改字符串长度N次必然需要执行N次内存重分配 修改字符串长度N次最多需要执行N次内存重分配 空间预分配策略
只能保存文本数据 可以保存文本或者二进制数据 C字符串除了末尾不能有空字符,因此不能八平村二进制数据
可以使用所有<string.h>库中的函数 可以使用一部分<string.h>库中的函数 因为SDS遵循了C字符串以空字符结尾的惯例

3. 链表和链表节点

每个链表节点用listNode结构表示

typedef struct listNode {
    //前置节点
    struct listNode *prev;
    //后置节点
    struct listNode *next;
    //节点的值
    void *value;
}listNode;

链表结构用list结构表示

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;

Redis的链表实现的特性:

双端

链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。

无环

表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。

带表头指针和表尾指针

通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)。

带链表长度计数器

程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。

多态

链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。

4. 字典的实现

4.1 字典结构

哈希表

typedef struct dichht {
    //哈希表数组
    dictEntry **table;
    //哈希表大小
    unsigned long siez;
    //哈希表大小掩码,用于计算索引值,总是等于size -1
    unsigned long sizemark;
    //该哈希表已有节点的数量
    unsigned long used;
}dictht;

 哈希表节点

typedef struct dictEntry {
    //键
    void *key;
    //值
    union {
        void *val;
        uint64_tu64;
        int64_ts64;
    }v;
    //指向下个哈希表节点,形成链表
    struct dictEntry *next;
}dictEntry;

        v属性保存键值对中的值,其中键值对的值可以是以一个指针,或者一个uint64_t整数,又或者是一个int64_t整数

 字典

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

        type属性和privdata属性是针对不同类型的键值对,为创建多态字典而设置的:

                type属性是以恶搞只想dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数

                privdata属性保存了需要传给那些类型特定函数的可选参数

        ht属性是一个包含两项的数组,数组的每一项都是一个dictht哈希表,一般情况下,字典只用ht[0]哈希表,ht[1]哈希表只在对ht[0]哈希表进行rehash时使用

 4.2 哈希算法

        先根据键值对的key计算出hash和index,再根据index将该哈希表节点放到哈希表数组的指定index

hash = dict -> type -> hashFunction(key)
index = hash & dict ->ht[x].sizemark  (rehash 时 x= 1,没有rehash时 x =0)

        当字典被用于数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash2算法来计算键的哈希值

4.3 哈希冲突

        Redis的哈希表通过每个哈希表节点的next指针来连接索引相同的哈希表节点,采取头插法

4.4 rehash

        随着操作的不断执行,哈希表保存的键值对会增多或减少,为了保证哈希表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值