共读《redis设计与实现》-数据结构篇

Python微信订餐小程序课程视频

https://blog.csdn.net/m0_56069948/article/details/122285951

Python实战量化交易理财系统

https://blog.csdn.net/m0_56069948/article/details/122285941
准备将之前攒下的书先看一遍,主要是有个大概的了解,以后用的时候也知道在哪里找。所以准备开几篇共读的帖子,激励自己多看一些书。

Redis 基于 简单动态字符串(SDS)、双端链表字典压缩列表整数集合等基础的数据结构,创建了一个对象系统,这个对象系统包含:字符串对象(String)、列表对象(List)、集合对象(Set)、有序集合对象(Zset)、哈希对象(Hash) 5种数据对象类型。但是这5种对象类型,其内部的基础的存储结构 并不是 一对一的一种,而是每一种包含了至少两种数据结构。

我们这篇主要用来说一下其基础的存储结构

前提条件

redis 底层是使用C语言编写的,所以很多函数直接使用的C库。

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

我们知道C语言中字符串 是以字符数组char[]进行存储的,字符串的结束是以 空字符‘/0’ 来进行标识的,也就是字符串的实际长度比我们看见的字符串都会多1 byte(字节)
如果我们想要查看一下字符串的长度,那么就需要遍历一下字符数组,时间复杂度为O(n)。
![image.png](https://img-blog.csdnimg.cn/img_convert/5b72b6cb8f3bf497d4470c9204e57680.png#clientId=u45edc488-58e8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=268&id=u4707854a&margin=[object Object]&name=image.png&originHeight=268&originWidth=932&originalType=binary&ratio=1&rotation=0&showTitle=true&size=52829&status=done&style=none&taskId=u13de9252-8aca-44a5-a16c-2560ebf0af0&title=SDS结构图&width=932 “SDS结构图”)

1.1 结构说明:

  1. redis中使用结构体SDS用来存储字符串类型,同样的使用字符数组进行存储 也自带空字符‘/0’,从而可以使用C语言中字符串相关的特性/函数。
  2. len:数组已用长度记录,就是说字符串的真实长度(不算‘/0’)
  3. free:数组中剩余可用长度,也就是数组中还有多少长度使用的。

1.2 内存预分配

我们从SDS结构图可以知道SDS中字符数组的长度是和字符串长度不一样的,那么这个长度是如何分配的?

  1. 首先如果是创建/扩展:
    1. 小于1M,分配的 未使用内存 是 使用内存的2倍
    2. 大于1M,那么 每次扩展未使用内存为 1M
  2. 如果是收缩:

并不会立即真正释放,会留下未使用的内存,可以通过Api来进行释放,从而避免内存泄漏

1.3 二进制

由于C语言中字符串以 ‘/0’标识结尾,所以C语言中字符串不能存储 图片、音视频的二进制数据,但是redis 中字符串以len来做为结尾的判断,所以可以使用字符串来存储二进制的数据。
当然对于 文本类型的 本身结束就是‘/0’结尾的,所以我们可以直接使用C的字符串特性。

1.4 特性(总结):

  1. 自带空格,从而可以使用C语言字符串相关特性
  2. 存储 使用空间未使用空间这样长度可以快速得出(时间复杂度O(1)),不用遍历数组(时间复杂度O(n))
  3. 由2我们可以杜绝 C语言中缓存溢出的问题
  4. 节省了避免缓存溢出而带来 内存重分配的系统开销
  5. 空间预分配
    1. 扩展:小于1M 预分配未使用空间为 使用空间的2倍,大于1M,预分配未使用空间为1M;
    2. 收缩:惰性空间释放
  6. 可以存储图片和音视频二进制数据。

关于 C语言缓存溢出:
我们知道数组是一块内存挨着的存储空间,C语言中,如果我们直接对字符串增加,会有如下这种情况的发生:
![image.png](https://img-blog.csdnimg.cn/img_convert/d4d3b3a872442267c5484c6b7c182f79.png#clientId=u45edc488-58e8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=298&id=u43dc17ec&margin=[object Object]&name=image.png&originHeight=298&originWidth=1832&originalType=binary&ratio=1&rotation=0&showTitle=true&size=49201&status=done&style=none&taskId=ub6e75eb1-2133-4bf1-9bf9-f7fa24fb256&title=字符串“hello”未添加 字段之前内存快照&width=1832 “字符串“hello”未添加 字段之前内存快照”)
现在给hello 尾部添加 “-wi” 字符串
image
“字符串“hello”添加 “-wi” 字符串之后内存快照”

所以C语言中我们为了防止这种情况,每次扩展的时候都会进行 内存重分配,使得空余的字符数组可以容得下我们新加的字符串。但是 内存重分配会导致系统调用,对于redis这种频繁增加删除的数据库来说,这种肯定要尽可能的减少系统性能的浪费。

二、链表

其实就是一个结构体持有双向链表

Copytypedef struct list{
    //表头节点
    listNode *head;
    //表尾节点
    listNode *tail;
    //链表所包含的节点数量
    unsigned long len;
    //节点值复制函数
    void *(*dup)(void *ptr);
    //节点值释放函数
    void *(*free)(void *ptr);
    //节点值对比函数
    int (*match)(void *ptr,void *key);
}list;

![image.png](https://img-blog.csdnimg.cn/img_convert/bfa4ed8ce68f2b31199fc8d0b8d3be7a.png#clientId=u45edc488-58e8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=122&id=uaf0d2abe&margin=[object Object]&name=image.png&originHeight=122&originWidth=528&originalType=binary&ratio=1&rotation=0&showTitle=true&size=22779&status=done&style=none&taskId=u62ebd04a-a911-4b2f-aa0f-b081eab36cb&title=redis链表结构图&width=528 “redis链表结构图”)

特性

  1. 双向连表,这样查找前(或者后)一个节点,复杂度为O(1)
  2. 有头尾指针,查找第一个节点、最后一个节点复杂度为O(1)
  3. 带链表长度计数器,返回长度复杂度为O(1)
  4. 无环(⚠️)
  5. void* 存储节点的值,可以使用dup\free\match 等特定函数。

三、字典

C语言本身没有 字典类型,但是对于key-vale 这种映射的关系 在redis是常用的,所以redis 自己构建了一个结构体,本身使用的是 hash 结构

Copytypedef struct dict {
    dictType *type;     //dictType也是一种数据结构,dictType结构中包含了一些函数,这些函数用来计算key的哈希值,进而用这个哈希值计算key在dictEntry型table数组中的下标
    void *privdata;     //私有数据,保存着dictType结构中函数的参数
    dictht ht[2];       //两张哈希表:一张用来正常存储节点,一张用来在rehash时临时存储节点
    long rehashidx;     //rehash的标记:默认-1,当table数组中已有元素个数增加/减少到一定量时,整个字典结构将进行rehash给每个table元素重新分配位置,rehashidx代表rehash过程的进度,rehashidx==-1代表字典没有在进行rehash,rehashidx>-1代表该字典结构正在对进行rehash
} dict;

3.1 字典结构体

  1. dictType:也是一种数据结构,dictType结构中包含了一些函数(dup\free等),这些函数用来计算key的哈希值,进而用这个哈希值计算key在dictEntry型table数组中的下标。

说白了,也就是redis 的字典为每种基础类型都创建了一个dictType,使得可以使用类型特定的函数

  1. privdata:私有数据,存储dictType构造参数,不同的类型传不同 的参数
  2. ht[]:哈希表,真正存储数据的地方。其中ht[0]是使用的表,ht[1]没有分配内存空间,只有在rehash的时候会分配内存,用到。
  3. rehashidx:在rehash的时候才会使用。
3.1.1 redis 哈希表结构体:
Copytypedef struct dictht { //哈希表
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask; 
    unsigne
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值