- 1、本文档共21页,可阅读全部内容。
- 2、原创力文档(book118)网站文档一经付费(服务费),不意味着购买了该文档的版权,仅供个人/单位学习、研究之用,不得用于商业用途,未经授权,严禁复制、发行、汇编、翻译或者网络传播等,侵权必究。
- 3、本站所有内容均由合作方或网友上传,本站不对文档的完整性、权威性及其观点立场正确性做任何保证或承诺!文档内容仅供研究参考,付费前请自行鉴别。如您付费,意味着您自己接受本站规则且自行承担风险,本站不退款、不进行额外附加服务;查看《如何避免下载的几个坑》。如果您已付费下载过本站文档,您可以点击 这里二次下载。
- 4、如文档侵犯商业秘密、侵犯著作权、侵犯人身权等,请点击“版权申诉”(推荐),也可以打举报电话:400-050-0827(电话支持时间:9:00-18:30)。
查看更多
?
?
Redis设计与实现
简单动态字符串、链表、字典
?
?
前言
《Redis设计与实现》数据结构部分有关字符串类型介绍。
文章目录
前言
一、数据结构——简单动态字符串
1.1 SDS定义
1.2 SDS与C字符串的区别
1.2.1常数复杂度获取字符串长度
1.2.2 杜绝缓冲区溢出
1.2.3 减少修改字符串时带来的内存重分配次数
空间预分配
惰性空间释放
1.2.4 二进制安全
1.2.5 兼容部分C 字符串函数
1.3 SDS主要操作API
二、链表
2.1 链表的实现
2.2 Redis链表特性
三、字典
3.1 字典的实现
3.1.1 哈希表 dictht
3.1.2 哈希表节点 dictEntry
3.1.3 字典 dict
3.2 哈希算法
3.3 解决键冲突
3.4 rehash
3.4.1 rehash概念
3.4.2 rehash执行步骤
3.4.3 哈希表扩展与收缩
3.5 Copy-on-write 写时复制
3.5.1 概念介绍
3.5.2 过程
3.5.3 原理
一、数据结构——简单动态字符串
1.1 SDS定义
在Redis中,没有直接使用C语言的字符串来表示,而是采用自己构建的简单动态字符串(simple dynamic string,SDS) 的抽象类型,并将 SDS 用作默认字符串表示。Redis中,C字符串只会用在一些无须对字符串值进行修改的地方,比如说打印日志。而SDS用来保存数据库中的字符串、AOF模块中的缓冲区、客户端输入缓冲区。
SDS的结构:
struct sdshdr {
// 记录buf数组中已使用字节的数量
// 等于SDS所保存字符串的长度
int len;
// 记录buf数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
}
SDS遵循C字符串以空字符结尾的惯例,保存空字符的1字节空间不计算在SDS的 len 属性里面,并且为空字符分配额外的1字节空间,以及添加空字符到字符串末尾等操作,都是由SDS 函数自动完成的。由于结构上的类似,SDS字符串也可以直接重用一部分C字符串函数库里面的函数。
1.2 SDS与C字符串的区别
1.2.1常数复杂度获取字符串长度
C字符串的最后一个元素总是空字符 \0,需要使用长度为N+1的字符数组来表示长度为N的字符串。由于C字符串本身不记录数组长度,因此需要遍历整个数组来获取字符串长度,这个操作的复杂度为 O(N);而 SDS 在len属性中记录了 SDS 本身的长度,所以获取长度的复杂度为 O(1);
通过使用 SDS 而不是 C字符串,Redis将获取字符串长度所需的复杂度从 O(N) 降低到了 O(1),这确保了获取字符串长度的工作不会成为 Redis的性能瓶颈。
1.2.2 杜绝缓冲区溢出
C字符串C字符串不记录自身长度带来的另一个问题就是容易造成缓冲区溢出。比如说 string.h/strcat 函数可以将 src字符串中的内容拼接到 dest字符串的末尾:char *strcat(char *dest,const char *src);假设原程序里存在两个内存中紧邻着的C字符串 s1和s2,分别保存了 “Redis” 、“MongoDB”,如果执行 strcat(s1, cluster) 那么总的字符串长度就超过了已经分配的空间大小,在没有重新分配空间的前提下就会出现内存溢出的问题。
SDS字符串与C字符串不同,SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性:当SDS 需要对 SDS进行修改时,API会首先检查 SDS空间是否满足修改所需的要求,如果不满足的话,API会自动将 SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作。在扩展空间时,除了扩展必要的空间,还会分配额外的空间给SDS(free部分)。
1.2.3 减少修改字符串时带来的内存重分配次数
由于C字符串不记录本身的长度,因此对于一个包含N个字符的C字符串来说,这个C字符串的底层实现总是一个N+1个字符长的数组。所以每次增长或者缩短一个C字符串,程序都要对保存这个C字符串的数组进行一次内存重分配操作。而内存重分配涉及复杂的算法,并且可能需要执行系统调用,所以它通常是一个比较耗时的操作。
如果程序执行的是增长字符串的操作,比如拼接操作(append),那么在执行这个操作之前,程序需要先通过内存重分配来扩展底层数组的空间大小,否则会出现缓冲区溢出的问题。
如果程序执行的是缩短字符串的操作,比如截断操作(trim),那么在执行这个操作之后,程序需要先通过内存重分配来释放字符串不再使用的那
文档评论(0)