数据结构与算法笔记:实战篇 - 剖析Redis常用数据类型对应的数据结构

news2024/10/7 4:24:02

概述

从本章开始,就进入实战篇的部分。这部分主要通过一些开源醒目、经典系统,真枪实弹地教你,如何将数据结构和算法应用到项目中。所以这部分的内容,更多的是知识点的回顾,相对于基础篇和高级篇,其实这部分内容会更加容易看懂。

不过,希望你不要看懂就完了。要多举一反三,自己接触过的项目、基础框架、中间件中,都用过哪些数据结构和算法。你可以想一想,在自己做的项目中,有哪些可以用学过的数据结构和算法进一步优化。这样的学习效果才会更好。好了,本章就带你看下,经典数据库 Redis 中常用到的数据类型,底层都是用哪种数据结构和算法实现的?


Redis 数据库介绍

Redis 是一种键值(key-value)数据库。相对于关系型数据库(比如 MySQL),Redis 也被叫作非关系型数据库

关于 Redis 数据库,本人也学习过 Redis 核心技术教程,感兴趣的朋友可以去看下专栏。

像 MySQL 这样的关系型数据库,表的结构比较复杂,会包含很多字段,可以通过 SQL 语句,来实现非常复杂的查询需求。而 Redis 中只包含 “键” 和 “值” 两部分,只能通过 “键” 来查询值。真实因为这样简单的存储结构,也让 Redis 的读写效率非常高。

此外,Redis 主要是作为内存数据库来使用,也就是说,数据是存储在内存中。尽管它经常被用作内存数据库,但是它也支持将数据存储在磁盘中。这一点后面会介绍。

Redis 中,键的数据类型是字符串,但是为了丰富数据存储的方式,方便开发中使用,值的数据类型有很多,常用的数据类型有这样几种,它们分别是字符串、列表、字典、集合、有序集合。

“字符串”(String)这样的数据结构非常简单,对应到数据结构里,就是字符串。你应该非常熟悉,这里就不过多介绍了。我们着重看下其他四种比较复杂点的数据类型,看看它们底层都依赖了哪些数据结构。

列表(list)

我们先看列表。列表这种数据类型支持存储一组数据。这种数据类型对应两种实现方式,一种是压缩列表(ziplist),另一种是双向循环链表

当列表中的数据量比较小的时候,列表可以采用压缩列表的方式实现。具体需要同时满足下面两个条件:

  • 列表中保存的单个数据(有可能是字符串类型的)小于 64 字节。
  • 列表中的数据个数小于 512 个。

关于压缩列表,这里稍微解释一下。它并不是基础数据结构,而是 Redis 自己设计的一种数据存储结构。它有点类似数组,通过一片连续的内存空间,来存储数据。不过,它跟数组不同的一点是,它允许存储的数据大小不同。具体的存储结构也非常简单,你看下下面这幅图。

在这里插入图片描述

现在,我们来看看,压缩列表中的 “压缩” 两个字该如何理解?

听到 “压缩” 两个字,直观的反应是节省内存。之所以说这种存储结构节省内存,是相较于数组的存储思路而言。我们知道,数组要求每个元素的大小相同,如果我们要存储不同长度的字符串,那我们就需要用最大长度的字符串大小作为元素的大小(假设是 20 个字节)。当我们存储小于 20 个字节长度的字符串时,便会浪费部分存储空间。

在这里插入图片描述

压缩列表这种存储结构,一方面是比较节省空间,另一方面可以支持不同类型数据的存储。而且,因为数据存储在一片连续的空间,通过键来获取值为列表类型的数据,读取的效率也非常高。

当列表中存储的数据量比较大的时候,也就是不能同时满足刚刚讲的两个条件时,列表就要通过双向链表来实现了。

在链表章节中,我们已经讲过双向链表这种数据结构了。这里我们着重看一下 Redis 中双向链表的编码实现方式。

Redis 的这种双向链表的实现方式,非常值得借鉴。它额外定义一个 list 结构体,来组织链表的首、尾指针,还有长度等信息。这样,在使用的时候就会非常方便。

// 以下是C语言代码,因为Redis是C语音实现的
typedef struct listnode {
	struct listnode *prev;
	struct listnode *next;
	void *value;
} listnode;

typedef struct list {
	listnode *head;
	listnode *tail;
	unsigned long len;
	// ...省略其他定义
} list;

字典(hash)

字典类型用来存储一组数据对。每个数据对又包含键值两部分。字典类型也有两种实现方式,一种就是刚刚讲到的压缩列表,另一种是散列表

同样,只有当存储的数据量比较小的情况下,Redis 才使用散列表来实现字典类型。具体要满足两个条件:

  • 字典中保存的键和值的大小都要小于 64 字节。
  • 字典中键值对的数量小于 512 个。

当不能同时满足上面两个条件时,Redis 就使用散列表来实现字典类型。Redis 使用 MurmurHas2 这种运行速度快、随机性好的哈希算法作为哈希函数。对于哈希冲突,Redis 采用链表法来解决。此外,Redis 还支持散列表的动态扩容、缩容。

当数据动态增加之后,散列表的装载因子会不停地变大。为了避免散列表性能的下降,当装载因子大于 1 的时候,Redis 会触发扩容,将散列表扩大为原来大小的 2 倍左右(具体值需要计算才能得到,如果感兴趣,你可以去阅读源码)。

当数据动态减少之后,为了节省内存,当装载因子小于 0.1 的时候,Redis 就会触发缩容,缩小为字典中数据个数的大约 2 倍大小(这个值也是计算得到的,如果感兴趣,可以去阅读源码)。

前面讲过,扩容缩容要做大量的数据搬移和哈希值的重新计算,所以比较耗时。针对这个问题,Redis 使用我们在散列表(中)讲的渐进式扩容缩容策略,将数据的搬移分批进行,避免了大类数据一次性搬移导致的服务停顿。

集合(set)

集合这种数据类型用来存储一组不重复的数据。这种数据类型也有两种实现方式,一种是基于有序数组,另一种是基于散列表

当要存储的数据,同时满足下面两个条件时,Redis 就采用有序数组,来实现集合这种数据类型。

  • 存储的数据都是整数。
  • 存储的数据元素个数不超 512 个。

当不能同时满足这两个条件的时候,Redis 就使用散列表来存储集合中的数据。

有序集合(sortedset)

有序集合这种数据类型,我们在跳表章节已经讲过了。它用来存储一组数据,并且每个数据会附带一个得分。通过得分的大小,我们将数据组织成跳表这样的数据结构,以及支持快速地按照得分值、得分区间获取数据。

实际上,跟 Redis 的其他数据类型一样,有序集合也并不仅仅只有跳表这一组实现方式。当数据量比较小的时候,Redis 会用压缩列表来实现有序集合。具体点说就是,使用压缩列表来实现有序集合的前提,有这样两个:

  • 所有数据的大小都要小于 64 字节。
  • 元素个数要小于 128 个。

数据结构持久化

尽管 Redis 经常不会被用作内存数据库,但是它也支持数据落盘,也就是将内存中的数据存储到磁盘中。这样,当机器断电时,存储在 Redis 中的数据也不会丢失。在机器重启之后,Redis 只需要再将存储在硬盘中的数据,重新读取到内存,就可以继续工作了。

刚刚我们讲到,Redis 的数据格式由 “键” 和 “值 两部分组成。而 “值” 又支持很多数据类型,比如字符串、列表、字典、集合、有序集合。像字典、集合等类型,底层用到了散列表,散列表中有指针的概念,而指针指向的是内存中的存储地址。那 Redis 是如何将这样一个跟具体内存有关的数据结果存储到磁盘中的呢?

实际上,Redis 遇到的这个问题并不特殊,很多场景都会遇到。我们把它叫做数据结构持久化问题,或者对象持久化问题。这里的持久化,你可以笼统地理解为 “存储到磁盘”。

如何将数据结构持久化到硬盘?我们主要有两种解决思路。

第一种是清楚原有的存储结构,只将数据存储到磁盘中。当我们需要从磁盘还原数据到内存时,再重新将数据组织称原来的数据结构。实际上,Redis 采用的就是这种持久化思路。

不过,这种方式也有一定的弊端。那就是数据从磁盘还原到内存的过程,会耗费比较多的时间。比如,我们现在要将散列表中的数据存储到磁盘。当我们从磁盘中,取出数据重新构建散列表的时候,需要重新计算每个数据的哈希值。如果磁盘中存储的是几 GB 的数据,那重构数据结构的好事就不可忽视了。

第二种方式是保留原来的存储格式,将数据按照原有的个数存储在磁盘中。我们拿散列表这样的数据结构来举例。我们可以将散列表的大小、每个数据被散列到的槽的编号等信息,都保存在磁盘中。有了这些信息,我们从磁盘中奖数据还原到内存时,就可以避免重新计算哈希值。

总结

本章,我们学习了 Redis 中常用的数据类型底层依赖的数据结构,总结一下大概有这 5 种:压缩列表(可以看做一种特殊的数组)、有序数组链表散列表跳表。实际上,Redis 就是这些常用数据结构的封装。

你有没有发现,有了数据结构和算法的基础之后,再去阅读 Redis 的源码,理解起来就容易很多了?很多原来觉得很深奥的设计思想,是不是就都会觉得顺理成章了呢?

还是那句话,夯实基础很重要。通用是看源码,有些人只能看个热闹,了解一些皮毛,无法形成自己的知识结构,不能化为己用,过不了几天就忘了。而有些人基础很好,不但知其然,还能知其所以然,从而真正理解作者设计的动机。这样不但能有助于我们理解所用的开源软件,还能为我们自己的创新添砖加瓦。

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

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

相关文章

找不到mfc100.dll文件怎么办?推荐这7个解决方法快速解决mfc100.dll丢失问题

使用电脑中,会遇到各种各样的问题,比如找不到mfc100.dll,或mfc100.dll丢失导致软件程序无法继续运行,就是日常中比较常见的问题之一,今天我教大家遇到这个mfc100.dll丢失问题时候,要怎么解决,以…

多见线程方法

多见线程方法 本节的类代码可以查看上一节的类代码 线程暂停 Thread.sleep(1000);//暂停1000毫秒这就有点像在时间里面学习的*sleep()*函数了 package multiThread2;public class main {public static void main(String[] args) {Animal a1 new Animal("张三",1…

Springboot 整合 DolphinScheduler(一):初识海豚调度

目录 一、什么是 DolphinScheduler 二、DolphinScheduler 的特性 三、DolphinScheduler 核心架构 四、单机环境部署流程 1、下载安装包 2、上传至服务器,解压缩 3、单机启动 4、登录 dolphinscheduler UI 5、配置数据库【非必需】 (1&#xff…

timm中模型更换huggingface模型链接

现在timm默认使用huggingface的链接了,错误链接如下: (MaxRetryError("HTTPSConnectionPool(hosthuggingface.co, port443): Max retries exceeded with url: /timm/swinv2_tiny_window8_256.ms_in1k/resolve/main/model.safetensors (Caused by C…

Docker Compose 入门

想象一下在服务器上运行静态页面的场景。对于这项任务,NGINX 服务器是一个不错的选择。我们在 static-site/index.html 路径下有一个简单的 HTML 文件: 通过使用 Docker,我们将使用以下官方镜像运行 NGINX 服务器 docker run --rm -p 8080:…

C++ 之插件机制初试

C 之插件机制 C 插件架构允许一个应用程序以动态链接库(DLLs 在 Windows,或 .so 在 Unix-like 系统)的形式加载和使用插件。以下是构建 C 插件架构的一般步骤和考虑因素: 定义插件接口 首先,定义一个插件接口&#…

R包的4种安装方式及常见问题解决方法

R包的4种安装方式及常见问题解决方法 R包的四种安装方式1. install.packages()2. 从Bioconductor安装3. 从本地源码安装4. 从github安装 常见问题的解决1. 版本问题2. 网络/镜像问题3.缺少Rtools R包的四种安装方式 1. install.packages() 对于R自带的包的安装一般都可以通过…

LeetCode热题100刷题2:283. 移动零、11. 盛最多水的容器、15. 三数之和、42. 接雨水

283. 移动零 挺简单的没啥说的 class Solution { public:void moveZeroes(vector<int>& nums) {//快慢指针 // 快指针负责往前遍历&#xff0c;慢指针记录快指针遍历过的把0撵走的最后一个元素的位置// 然后快指针遍历完之后&#xff0c;慢指针到结尾直接赋0就行in…

Python逻辑控制语句 之 判断语句--if语句的基本结构

1.程序执行的三大流程 顺序 分支&#xff08;判断&#xff09; 循环 2.if 语句的介绍 单独的 if 语句,就是 “如果 条件成⽴,做什么事” 3.if 语句的语法 if 判断条件: 判断条件成立&#xff0c;执行的代码…

PyCharm 2024.1 版本更新亮点:智能编程,高效协作

目录 1. 前言2. 更新内容2.1 智能编码体验2.1.1 Hugging Face 文档预览2.1.2 全行代码补全 2.2 提升编辑器体验2.2.1 粘性行功能2.2.2 编辑器内代码审查 2.3 全新终端体验&#xff08;测试版&#xff09;2.3.1 新终端 Beta 2.4 智能助手&#xff08;特定版本和专业用户&#xf…

操作符详解(下) (C语言)

操作符详解下 操作符的属性1.优先级2.结合级 表达式求值1.整型提升2.如何进行整形提升呢&#xff1f;3.算术转换4.问题表达式解析 操作符的属性 C语言的操作符有2个重要的属性&#xff1a;优先级、结合性&#xff0c;这两个属性决定了表达式求值的计算顺序。 1.优先级 优先级…

MSPM0G3507——定时器例程讲解4——timx_timer_mode_periodic

以下示例以周期模式配置TimerG并切换LED。周期从500ms开始&#xff0c;每次切换减少50ms&#xff0c;直到周期为100ms&#xff0c;然后重复。设备在等待中断时保持待机模式 #include "ti_msp_dl_config.h"/* ((32KHz / (321)) * 0.5s) 45 - 1 495 due to N1 ticks …

时间复杂度计算

要求算法的时间复杂度时&#xff0c;我们可以分析给定表达式 的阶。让我们来逐步分析&#xff1a; 分析阶的定义&#xff1a; 当我们说一个表达式的时间复杂度是 ( O(g(n)) )&#xff0c;我们指的是当 ( n ) 趋近无穷大时&#xff0c;表达式的增长率与 ( g(n) ) 的增长率相似。…

【计算机网络仿真】b站湖科大教书匠思科Packet Tracer——实验11 IP数据报的发送和转发流程

一、实验目的 1.观察IP数据报的发送和转发流程&#xff1b; 二、实验要求 1.使用Cisco Packet Tracer仿真平台&#xff1b; 2.观看B站湖科大教书匠仿真实验视频&#xff0c;完成对应实验。 三、实验内容 1.构建网络拓扑&#xff1b; 2.观察主机发送IP数据报的过程 3.观察路…

pytest-命令行参数

命令行参数 使用 Pytest 执行用例时&#xff0c;我们经常都是通过命令行来执行的&#xff0c;有同学要说了&#xff0c;我一般是通过编辑器里面直接就执行了&#xff1b;在实际项目中编写用例调试用例&#xff0c;使用编辑器执行用例没问题&#xff0c;但在 CI 集成环境下&…

中霖教育:二级建造师能同时报名参加多个省份的考试吗?

【中霖教育口碑】【中霖教育好吗】 二级建造师考试能同时报名参加多个省份吗?原则上是可以的。 二级建造师的报名过程需满足各省份设定的特定标准&#xff0c;申请者需提供相应省份注册的工程建设企业的工作年限证明&#xff0c;并在报名表上加盖章以证明企业身份。 部分省…

【电源专题】为什么带电量计芯片的电池MOS保护要放在高侧

在实际的电量计电池开发中,发现一个很奇怪的现象。传统电池保护IC往往都是将充电保护和放电保护的两个MOS管放在低侧的。如下所示是文章:【电源专题】读一读单节锂电池保护IC规格书 可以看到M1和M2两个MOS管是放在PB-(也就是电池的负端),我们叫做低端。 而BQ28Z610电…

LC437.路径总和Ⅲ、LC207.课程表

给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只能从父节点到子节点&am…

【小沐学AI】Python实现语音识别(whisperX)

文章目录 1、简介1.1 whisper1.2 whisperX 2、安装2.1 安装cuda2.2 安装whisperX 结语 1、简介 1.1 whisper https://arxiv.org/pdf/2212.04356 https://github.com/openai/whisper Whisper 是一种通用语音识别模型。它是在各种音频的大型数据集上训练的&#xff0c;也是一个…

打开防火墙设置提示需要使用新应用以打开此windowsdefender

拿到一台新电脑&#xff0c;装好虚拟机。主机ping虚拟机正常&#xff0c;虚拟机上网也正常&#xff0c;但是虚拟机ping主机ping不通。根据我多年虚拟机使用经验&#xff0c;这显然是因为主机防火墙没关。但是当我准备关闭主机防火墙的时候&#xff0c;发现防火墙设置打不开。界…