1. SDS:简单动态字符串(simple dynamic string)

Redis没有直接使用C语言的字符串,而是自己构建了一种名为简单动态字符串类型,并将SDS用作Redis的默认字符串。

SDS的定义

struct sdshdr {

    // buf 中已占用空间的长度
int len; // buf 中剩余可用空间的长度
int free; // 字节数组
char buf[];
};

SDS与C字符串的区别

  1. SDS获取字符串长度复杂度为O(1), C字符串获取字符串长度复杂度为O(N);

    因为C字符串获取字符串并不记录自身长度,程序必须遍历整个字符串对每个字符串计数。这个操作的复杂度为O(N)

    SDS在len属性中记录了SDS本身长度,所以获取字符串长度复杂度为O(1)
  2. API是安全的,不会造成缓冲区溢出;

    C字符串不记录自身长度,如果忘了给字符串扩容执行字符串拼接就会造成溢出

    SDS拼接字符串之前会先通过free字段检测剩余空间能否满足需求,不能满足需求的就会扩容。
  3. 减少修改字符串带来的内存重分配次数;

    C字符串底层总是一个N+1个字符数组,所以每次增长或缩短一个字符串,程序总要对这个C字符串进行一次内存重分配。

    SDS实现空间预分配和惰性空间释放两种优化策略.
  4. 二进制安全

    C字符串只能保存文本数据

    SDS可以保存文本或者二进制数据

** 2. 链表 **

Redis的List(列表)和发布订阅,慢查询,监视器等功能都用到了链表

链表节点实现 adlist.h/listNode结构表示

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

该链表为双向链表,由多个listNode结点组成的链表结构图如下:

链表实现 adlist.h/list结构表示

// list 双端链表
typedef struct list { // 在c语言中,用结构体的方式来模拟对象是一种常见的手法 // 表头节点
listNode *head; // 表尾节点
listNode *tail; // 节点值复制函数
void *(*dup)(void *ptr); // 节点值释放函数
void(*free)(void *ptr); // 节点值对比函数
int(*match)(void *ptr, void *key); // 链表所包含的节点数量
unsigned long len; } list;
例:由一个list结构和三个listNode结构组成的链表
![](https://img2020.cnblogs.com/blog/1186190/202102/1186190-20210227163531303-2083424534.jpg) 链表提供表头指针head,表尾指针tail,以及链表长度计数器len,和封装了3个内置函数
1.dup函数:复制链表结点所保存的值
2.free函数:释放链表结点所保存的值
3.match函数:对比链表结点所保存的值和另一个输入值是否相等
这三个函数是用于实现多态链表所需的类型特定函数。

Redis链表实现特征总结

1.双端:获取某个结点的前驱和后继结点都是O(1)

2.无环:表头的prev指针和表尾的next指针都指向NULL,对链表的访问都是以NULL为终点

3.带表头指针和表尾指针:获取表头和表尾的复杂度都是O(1)

4.带链表长度计数器:len属性记录,获取链表长度O(1)

5.多态:链表结点使用void*指针来保存结点的值,并且可以通过链表结构的三个函数为结点值设置类型特定函数,所以链表可以保存各种不同类型的值

  1. 字典

    1. 字典的实现

    哈希节点使用dictEntry结构表示,每个dictEntry结构都保存着一个键值对。

    // dictEntry 哈希表节点
typedef struct dictEntry {
// 键
void *key; // 值
union {//值v的类型可以是以下三种类型
void *val;
uint64_t u64;
int64_t s64;
} v; // 指向下个哈希表节点,形成链表
struct dictEntry *next; } dictEntry;
Redis字典使用哈希表有dictht.h/dictht结构定义

typedef struct dictht {
// 哈希表数组, 每个元素都是一条链表
dictEntry **table; // 哈希表大小
unsigned long size; // 哈希表大小掩码,用于计算索引值
// 总是等于 size - 1
unsigned long sizemask; // 该哈希表已有节点的数量
unsigned long used; } dictht;
![](https://img2020.cnblogs.com/blog/1186190/202102/1186190-20210227171229827-765330912.png)
    // dict 字典
typedef struct dict { // 类型特定函数
dictType *type; // type里面主要记录了一系列的函数,可以说是规定了一系列的接口 // 私有数据
void *privdata; // privdata保存了需要传递给那些类型特定函数的可选参数 //两张哈希表
dictht ht[2];//便于渐进式rehash //rehash 索引,并没有rehash时,值为 -1
int rehashidx; //目前正在运行的安全迭代器的数量
int iterators; } dict;
* type 属性是一个指向dictType结构的指针,每个dictType结构保存了一族用于操作特定类型键值对的函数,Redis为用途不同的字典设置不同的类型特定函数。
* privdata 属性则保存了需要传递给那些类型特定函数的可选参数。
* ht是一个包含两个项的数组,数组每个项都是一个dictht哈希表,一般情况下只使用ht[0]哈希表,ht[1]只会对ht[0]哈希表进行rehash时使用。
* rehashidx它记录了rehash目前的进度,如果目前没有进行rehash,那么他的值为-1.
    // dictType 用于操作字典类型函数
typedef struct dictType { // 计算哈希值的函数
unsigned int(*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;
![](https://img2020.cnblogs.com/blog/1186190/202102/1186190-20210227171242872-913327145.png)
  1. 哈希算法

    使用字典类型设置的哈希函数击视键key的哈希值

    int hash = dict->type->hashFunction(key)

    使用哈希表的sizemask的属性和哈希值计算出索引值

    index = hash & dict->ht[0].sizemask;

    使用哈希表节点next指针构成单向链表解决哈希冲突。

  2. 扩展和收缩哈希表的恭祝通过执行rehash操作来完成步骤如下

    如果执行的是扩展操作,那么扩展ht[1]的大小为第一个大于等于ht[0].used*2的2的n此幂

    如果执行的是收缩操作,那么收缩ht[1]的大小为第一个大于等于ht[0].used的2的n此幂

    将保存在ht[0]中的所有键值对rehash到ht[1]上面:rehash指的是重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指点位置。

    当ht[0]包含的所有键值对都迁移到ht[1]之后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]重新创建一个空哈希表,为下一次rehash做准备。

    当以下条件中的任意一个被满足时,程序会自动开始对哈希表进行扩展操作

    1)服务器目前没有执行BGSAVE命令或者BGREWRITEOF命令,并且哈希表的负载因子大于等于1.

    2)服务器目前正在执行BGSAVE命令或者BGREWRITEOF命令,并且哈希表的负载因子大于等于5.

    3) 哈希表的负载因子可以通过公式:load_factor = ht[0].used / ht[0].size;

    4) 哈希表的负载因子小于0.1时,自动执行哈希表收缩操作;

  3. 如果哈希表中有成千上万个键值对,那么要一次性rehash到ht[1]的话,可能会导致服务器一段时间内停止服务。为了避免rehash对服务器性能影响,服务器二十分多次,渐进性的将ht[0]里面的键值对渐进性的rehash。详细步骤:

    1)为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表。

    2)在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash工作正式开始。

    3) 在rehash进行期间,每次对字典执行添加,删除,查找或者更新操作时,程序除了执行指定的操作外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],完成顺带操作后,程序将rehashidx属性的值加一.

    4) 随着字典操作的不断执行,最终在某个时间点,ht[0]的所有键值对会被rehash至ht[1]。这是将rehashidx设置为-1.表示rehash操作已执行完。

redis数据结构和对象一的更多相关文章

  1. Redis数据结构和对象三

    1.Redis 对象系统 Redis用到的所有主要数据结构,简单动态字符串(SDS).双端链表.字典.压缩列表.整数集合.跳跃表. Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些 ...

  2. redis数据结构和对象二

    跳跃表(skiplist) 跳跃表是一种有序数据结构.跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树简单,所有不 ...

  3. Redis学习笔记一:数据结构与对象

    1. String(SDS) Redis使用自定义的一种字符串结构SDS来作为字符串的表示. 127.0.0.1:6379> set name liushijie OK 在如上操作中,name( ...

  4. Redis 基础数据结构与对象

    Redis用到的底层数据结构有:简单动态字符串.双端链表.字典.压缩列表.整数集合.跳跃表等,Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包 ...

  5. redis 系列9 对象类型(字符串,哈希,列表,集合,有序集合)与数据结构关系

    一.概述 在前面章节中,主要了解了 Redis用到的主要数据结构,包括:简单动态字符串.链表(双端链表).字典.跳跃表. 整数集合.压缩列表(后面再了解).Redis没有直接使用这些数据结构来实现键值 ...

  6. Redis 的底层数据结构(对象)

    目前为止,我们介绍了 redis 中非常典型的五种数据结构,从 SDS 到 压缩列表,这都是 redis 最底层.最常用的数据结构,相信你也掌握的不错. 但 redis 实际存储键值对的时候,是基于对 ...

  7. [redis读书笔记] 第一部分 数据结构与对象 对象类型

    - 从前面redis的基本数据结构来看,可以看出,redis都是在基本结构(string)的基础上,封装了一层统计的结构(SDS),这样让对基本结构的访问能够更快更准确,提高可控制度. - redis ...

  8. Redis | 第一部分:数据结构与对象 上篇《Redis设计与实现》

    目录 前言 1. 简单动态字符串 1.1 SDS的定义 1.2 空间预分配与惰性空间释放 1.3 SDS的API 2. 链表 2.1 链表与节点的定义 2.2 链表的API 3. 字典 3.1 哈希表 ...

  9. Redis | 第一部分:数据结构与对象 下篇《Redis设计与实现》

    目录 前言 1. Redis对象概述 1.1 对象的定义 2. 字符串对象 3. 列表对象 3.1 quicklist 快速链表 4. 哈希对象 5. 集合对象 6. 有序集合对象 7. Redis对 ...

随机推荐

  1. windows10与linux进行ftp遇到550 Failed to change directory及553 Could not creat file

    第一个原因: 没有权限,可以使用带有l参数的ls命令来看文件或者目录的权限 ls -l 解决:给本地用户添加一个可写权限 chmod +w /home/student ##给对应的本地用户添加一个可写 ...

  2. zjnu1716 NEKAMELEONI (线段树)

    Description "Hey! I have an awesome task with chameleons, 5 th task for Saturday's competition. ...

  3. Codeforces Round #650 (Div. 3) E. Necklace Assembly (暴力)

    题意:有一个字符串,要求使用其中字符构造一个环(不必全部都用),定义一个环是k美的,如果它转\(k\)次仍是原样,现在给你\(k\),要求最长的k美环的长度. 题解:我们首先看\(k\),如果一个环转 ...

  4. Dapr微服务应用开发系列0:概述

    题记:Dapr是什么,Dapr包含什么,为什么要用Dapr. Dapr是什么 Dapr(Distributed Application Runtime),是微软Azure内部创新孵化团队的一个开源项目 ...

  5. httprunner(8)用例调用-RunTestCase

    前言 一般我们写接口自动化的时候,遇到复杂的逻辑,都会调用API方法来满足前置条件,Pytest的特性是无法用例之间相互调动的,我们一般只调用自己封装的API方法. 而httprunner支持用例之间 ...

  6. Linux内核4.19.1编译

    linux内核编译 1.1 大致步骤 下载linux内核4.19.1 官网链接: https://www.kernel.org/ 官网下载经常速度太慢,无法下载,提供另一个链接: http://ftp ...

  7. Spring(三) Spring IOC

    Spring 核心之 IOC 容器 再谈 IOC 与 DI IOC(Inversion of Control)控制反转:所谓控制反转,就是把原先我们代码里面需要实现的对象创 建.依赖的代码,反转给容器 ...

  8. 渗透技巧——如何逃逸Linux的受限制shell执行任意命令

    导语:本文介绍了如何在受限制的shell中执行任意命令,实现交互.其相应的利用场景是说当我们通过一些手段拿到当前Linux机器的shell时,由于当前shell的限制,很多命令不能执行,导致后续的渗透 ...

  9. Linux bash fi

    Linux bash fi if..else..fi allows to make choice based on the success or failure of a command. if..e ...

  10. SSL/TLS All In One

    SSL/TLS All In One HTTPS SSL/TLS 的工作原理 https://www.websecurity.digicert.com/zh/cn/security-topics/ho ...