redis(2)——redis的数据结构

本文介绍了Redis内部使用的关键数据结构,包括简单动态字符串(SDS)、链表、字典、跳跃表、整数集合和压缩列表等。每种数据结构的特点和应用场景均有详细说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

根据书籍《redis设计与实现》总结。

一、简单动态字符串

在redis数据库里面,包含字符串值的键值对在底层都是由SDS实现的。

1、SDS数据结构

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

特点:

(1)、常数复杂度获取字符串长度

(2)、杜绝缓冲区溢出

       当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足的话,API会自动将SDS的空间扩展至执行修改所需要的大小,然后在执行实际的修改操作,所以使用SDS既不需要手动修改SDS的空间大小,也不会出现前面所说的缓冲区溢出问题。

3、减少修改字符串是带来的内存重分配次数

3.1 空间预分配

空间预分配用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间。

3.2 惰性预分配

惰性空间释放用于优化SDS的字符串缩短操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录下来,并等待将来使用。

4、二进制安全

SDS API都会以处理二进制的方式来处理SDS存放在buf数组里面的数据,程序不会对其中的数据做任何限制、过滤、或者假设,数据写入时是什么样的,它被读取时就是什么样。

5、兼容部分C字符串函数

二、链表

链表节点:

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


链表:

/* 双向链表的定义 */  
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;

三、字典

字典,又称为符号表、关联数组或映射,是一种用于保存键值对的抽象数据结构。

Redis数据库就是字典来作为底层实现的,对数据库的增、删、改、查操作也是构建对字典的操作之上的。

字典还是哈希键的底层实现之一,当一个哈希键包含的键值对比较多,又或者键值对中的元素都是比较城的字符串时,Redis就会使用字典作为哈希键的底层实现。

1、字典的结构

Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。

/* 哈希表结构 */  
typedef struct dictht {  
    // 散列数组。  
    dictEntry **table;  
    // 散列数组的长度  
    unsigned long size;  
    // 哈希表大小掩码,用于计算索引值,等于size减1  
    unsigned long sizemask;  
    // 散列数组中已经被使用的节点数量  
    unsigned long used;  
} dictht;
/* 哈希表节点*/  
typedef struct dictEntry {  
    // 关键字key定义  
    void *key;    
    // 值value定义,只能存放一个被选中的成员  
    union {  
        void *val;        
        uint64_t u64;     
        int64_t s64;      
        double d;         
    } v;  
    // 指向下一个键值对节点,形成链表
    struct dictEntry *next;  
} dictEntry;
/* 字典的主操作类,对dictht结构再次包装  */
typedef struct dict {
    // 字典类型
    dictType *type;
    // 私有数据
    void *privdata;
    // 一个字典中有两个哈希表
    dictht ht[2];
    // 数据动态迁移的下标位置
    long rehashidx; 
    // 当前正在使用的迭代器的数量
    int iterators; 
} dict;
/* 定义了字典操作的公共方法 */  
typedef struct dictType {  
    /* hash方法,根据关键字计算哈希值 */  
    unsigned int (*hashFunction)(const void *key);  
    /* 复制key */  
    void *(*keyDup)(void *privdata, const void *key);  
    /* 复制value */  
    void *(*valDup)(void *privdata, const void *obj);  
    /* 关键字比较方法 */  
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);  
    /* 销毁key */  
    void (*keyDestructor)(void *privdata, void *key);  
    /* 销毁value */  
    void (*valDestructor)(void *privdata, void *obj);  
} dictType;


2、字典的操作

2.1 哈希算法

当要将一个新的键值对添加到字典里面时,程序需要先根据键值对的键计算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上面。

2.2 解决键冲突

Redis的哈希表使用链地址法来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来。

2.3 rehash和渐进式rehash

《redis设计与实现》P29

四、跳跃表

redis的跳跃表结构设计的真心没看懂。《redis设计与实现》P38

五、整数集合

1、整数集合的数据结构

/* 整数集合结构体 */
typedef struct intset {
    // 编码方式
    uint32_t encoding;
    // 集合中包含的元素数量
    uint32_t length;
    // 真正保存元素的数组
    int8_t contents[];
} intset;

content数组是整数集合的底层实现:整数集合的每个元素都是content数组的一个数组项,各个项在数组中按值的大小从小到大有序地排列,并且数组中不包含任何重复项。content数组的真正类型取决于encoding属性的值。

2、升级

升级整数集合并添加新元素共分为三步进行:

1)根据新元素的类型,扩展整数集合底层数组的空间大小,并未新元素分配空间。

2)将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变。

3)将新元素添加到底层数组里面。

升级的好处:提升灵活性;节约内存

3、降级

整数集合不支持降级操作,一旦对数组进行了升级,编码就会一直保持升级后的状态

六、压缩列表

1、压缩列表的结构



2、压缩列表节点的结构


previous_entry_length:记录了压缩列表中前一个点的长度。

encoding:记录了节点的content属性所保存数据的类型和长度。

content:保存节点的值,节点值可以是一个字节数组或者是整数,值的类型和长度由节点的encoding属性决定。

3、连锁更新

《redis设计与实现》P57








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值