redis——数据结构(字典、链表、字符串)

1 字符串

redis并未使用传统的c语言字符串表示,它自己构建了一种简单的动态字符串抽象类型。

在redis里,c语言字符串只会作为字符串字面量出现,用在无需修改的地方。

当需要一个可以被修改的字符串时,redis就会使用自己实现的SDS(simple dynamic string)。比如在redis数据库里,包含字符串的键值对底层都是SDS实现的,不止如此,SDS还被用作缓冲区(buffer):比如AOF模块中的AOF缓冲区以及客户端状态中的输入缓冲区。

下面来具体看一下sds的实现:

struct sdshdr
{int len;//buf已使用字节数量(保存的字符串长度)int free;//未使用的字节数量char buf[];//用来保存字符串的字节数组
};

sds遵循c中字符串以'\0'结尾的惯例,这一字节的空间不算在len之内。

这样的好处是,我们可以直接重用c中的一部分函数。比如printf;

    sds相对c的改进

    获取长度:c字符串并不记录自身长度,所以获取长度只能遍历一遍字符串,redis直接读取len即可。

    缓冲区安全:c字符串容易造成缓冲区溢出,比如:程序员没有分配足够的空间就执行拼接操作。而redis会先检查sds的空间是否满足所需要求,如果不满足会自动扩充。

    内存分配:由于c不记录字符串长度,对于包含了n个字符的字符串,底层总是一个长度n+1的数组,每一次长度变化,总是要对这个数组进行一次内存重新分配的操作。因为内存分配涉及复杂算法并且可能需要执行系统调用,所以它通常是比较耗时的操作。   

    redis内存分配:

1、空间预分配:如果修改后大小小于1MB,程序分配和len大小一样的未使用空间,如果修改后大于1MB,程序分配  1MB的未使用空间。修改长度时检查,够的话就直接使用未使用空间,不用再分配。 

2、惰性空间释放:字符串缩短时不需要释放空间,用free记录即可,留作以后使用。

    二进制安全

c字符串除了末尾外,不能包含空字符,否则程序读到空字符会误以为是结尾,这就限制了c字符串只能保存文本,二进制文件就不能保存了。

而redis字符串都是二进制安全的,因为有len来记录长度。

2 链表

作为一种常用数据结构,链表内置在很多高级语言中,因为c并没有,所以redis实现了自己的链表。

链表在redis也有一定的应用,比如列表键的底层实现之一就是链表。(当列表键包含大量元素或者元素都是很长的字符串时)

发布与订阅、慢查询、监视器等功能也用到了链表。

具体实现:

//redis的节点使用了双向链表结构
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;

总结一下redis链表特性:

双端、无环、带长度记录、

多态:使用 void* 指针来保存节点值, 可以通过 dup 、 free 、 match 为节点值设置类型特定函数, 可以保存不同类型的值。

3、字典

其实字典这种数据结构也内置在很多高级语言中,但是c语言没有,所以redis自己实现了。

应用也比较广泛,比如redis的数据库就是字典实现的。不仅如此,当一个哈希键包含的键值对比较多,或者都是很长的字符串,redis就会用字典作为哈希键的底层实现。

来看看具体是实现:

//redis的字典使用哈希表作为底层实现
typedef struct dictht {// 哈希表数组dictEntry **table;// 哈希表大小unsigned long size;// 哈希表大小掩码,用于计算索引值// 总是等于 size - 1unsigned long sizemask;// 该哈希表已有节点的数量unsigned long used;} dictht;

table 是一个数组, 数组中的每个元素都是一个指向dictEntry 结构的指针, 每个 dictEntry 结构保存着一个键值对

图为一个大小为4的空哈希表。

我们接着就来看dictEntry的实现:

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

(v可以是一个指针, 或者是一个 uint64_t 整数, 又或者是一个 int64_t 整数。)

next就是解决键冲突问题的,冲突了就挂后面,这个学过数据结构的应该都知道吧,不说了。

 

下面我们来说字典是怎么实现的了。

typedef struct dict {// 类型特定函数dictType *type;// 私有数据void *privdata;// 哈希表dictht ht[2];// rehash 索引int rehashidx; //* rehashing not in progress if rehashidx == -1 
} dict;

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

type 指向 dictType , 每个 dictType 保存了用于操作特定类型键值对的函数, 可以为用途不同的字典设置不同的类型特定函数。

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

而dictType就暂时不展示了,不重要而且字有点多。。。还是讲有意思的东西吧

    rehash(重新散列)

随着我们不断的操作,哈希表保存的键值可能会增多或者减少,为了让哈希表的负载因子维持在合理的范围内,有时需要对哈希表进行合理的扩展或者收缩。 一般情况下, 字典只使用 ht[0] 哈希表, ht[1] 哈希表只会在对 ht[0] 哈希表进行 rehash 时使用。

redis字典哈希rehash的步骤如下:

1)为ht[1]分配合理空间:如果是扩展操作,大小为第一个大于等于ht[0]*used*2的,2的n次幂。

                                           如果是收缩操作,大小为第一个大于等于ht[0]*used的,2的n次幂。

2)将ht[0]中的数据rehash到ht[1]上。

3)释放ht[0],将ht[1]设置为ht[0],ht[1]创建空表,为下次做准备。

    渐进rehash

数据量特别大时,rehash可能对服务器造成影响。为了避免,服务器不是一次性rehash的,而是分多次。

我们维持一个变量rehashidx,设置为0,代表rehash开始,然后开始rehash,在这期间,每个对字典的操作,程序都会把索引rehashidx上的数据移动到ht[1]。

随着操作不断执行,最终我们会完成rehash,设置rehashidx为-1.

需要注意:rehash过程中,每一次增删改查也是在两个表进行的。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445416.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hotspot虚拟机的对象

创建 Step1:类加载检查 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 Step2:分…

redis——数据结构(整数集合,压缩列表)

4、整数集合 整数集合(intset)是 Redis 用于保存整数值的集合抽象数据结构, 可以保存 int16_t 、 int32_t 、 int64_t 的整数值, 并且保证集合中不会出现重复元素。 实现较为简单: typedef struct intset {// 编码方…

机器学习知识总结系列- 知识图谱(0-0)

文章目录目录机器学习知识图谱目录 本系列的文章只是根据个人的习惯进行总结,可能结构与一些书籍上不太一样,开始的内容比较简单,会随着后续的深入,不断丰富和更新图谱,同时也期待有相同兴趣的朋友一起给我留言一起丰富…

跳表介绍和实现

想慢慢的给大家自然的引入跳表。 想想,我们 1)在有序数列里搜索一个数 2)或者把一个数插入到正确的位置 都怎么做? 很简单吧 对于第一个操作,我们可以一个一个比较,在数组中我们可以二分,这…

机器学习知识总结系列- 基本概念(1-0)

文章目录目录1. 机器学习的定义2. 机器学习的分类2.1根据是否在人类监督下进行训练监督学习非监督学习半监督学习强化学习2.2根据是否可以动态渐进的学习在线学习批量学习2.3根据是否在训练数据过程中进行模式识别实例学习基于模型的学习3. 机器学习中的一些常见名词4. 机器学习…

剑指offer(刷题21-30)--c++,Python版本

文章目录目录第 21题:解题思路:代码实现:cpython第22 题:解题思路:代码实现:cpython第23 题:解题思路:代码实现:cpython第24 题:解题思路:代码实现…

剑指offer(刷题41-50)--c++,Python版本

文章目录目录第41题:解题思路:代码实现:cpython第42题:解题思路:代码实现:cpython第43题:解题思路:代码实现:cpython第44题:解题思路:代码实现&am…

redis——持久化

因为redis是内存数据库,他把数据都存在内存里,所以要想办法实现持久化功能。 RDB RDB持久化可以手动执行,也可以配置定期执行,可以把某个时间的数据状态保存到RDB文件中,反之,我们可以用RDB文件还原数据库…

剑指offer(刷题51-60)--c++,Python版本

文章目录目录第51题:解题思路:代码实现:cpython第52题:解题思路:代码实现:cpython第53题:解题思路:代码实现:cpython第54题:解题思路:代码实现&am…

2017第一届河北省大学生程序设计竞赛题解

超级密码 小明今年9岁了,最近迷上了设计密码!今天,他又设计了一套他认为很复杂的密码,并且称之为“超级密码”. 说实话,这套所谓的“超级密码”其实并不难:对于一个给定的字符串,你只要提取其中…

大数的四则运算(加法、减法、乘法、除法)

大数的四则运算(加法、减法、乘法、除法) 前言: 在计算机中数字表示的范围是有限制的,比如我们熟知的 int、float、double 等数据类型所能表示的范围都是有限的,如果我们要对位数达到几十位、几百位、上千位的大整数进…

随机过程1

随机过程1概述1.参考书目2.主要内容3.概率论--基本概念回顾3.1对“不确定性”的认识3.2 应对“不确定性”应该怎么做3.3随机变量(Random Variable)3.4分布函数(Distribution Function)3.5概率密度(Density)…

数组基操三连(4)

题目一 给定一个长度为N的整型数组arr,其中有N个互不相等的自然数1~N 请实现arr的排序 但是不要把下标0~N-1位置上的数值通过直接赋值的方式替换成1~N。 要求:时间复杂度为O(N),额外空间复杂度为O(1)。 思路:从左向右检查&…

Linux(1)-touch,mkdir,rm,mv,cp,ls,cd,cat

Linux1-实用终端命令1. touch, mkdir2. rm, mv, cp3. ls(通配符),cd(绝对/相对路径)4. cat, more/less文件内容浏览文件/目录-增删查改, 文件内容查看.1. touch, mkdir touch新文件 :在当前文件夹下,创建文件。文件不存在则创建新文件;文件存…

数组精选题目三连(5)

子数组的最大累加和问题 输入一个整形数组,求数组中连续的子数组使其和最大。比如,数组x 应该返回 x[2..6]的和187. 这四个代码完成的功能都是求最大子数组(注意用词准确,子数组连续,子序列可以不连续)。…

大数据学习(1)-大数据概述

文章目录目录大数据产生背景大数据概念大数据影响大数据应用大数据关键技术大数据产业大数据,云计算,物联网关系云计算物联网大数据,物联网,云计算三者之间联系目录 大数据产生背景 三次信息化浪潮 根据IBM前首席执行官郭士纳福…

redis——事件

redis服务器是一个事件驱动程序。 需要处理两类事件: 1)文件事件:redis是通过套接字与客户端或者其他服务器连接的,而文件事件就是服务器对套接字操作的抽象。 2)时间事件:服务器对一些定时操作的抽象。…