前言
工作慢慢步入正轨,闲暇时间也多了起来,抽空充下电,岂不美哉?
想来想去,发现自己不足之处良多,何不选个使用较多的点作为切入,哈哈,于是有了这系列文章。作为记录,和大家分享。若有不足之处,还请言明,不胜感激。
本文主要是源自于对《Redis设计与实现》的学习,所以会有大量雷同,慎入。
介绍
众所周知,Redis有5种对象类型:string、list、hash、set、zset(排除Redis于2.8.9中新加入的HyperLogLog);这5种对象类型底层其实是由6种基础数据结构单个或多个组合实现,分别是:
- 简单动态字符串(SDS:Simple Dynamic String)
- 链表(Linked List)
- 字典(Dict)
- 跳跃表(Skip List)
- 整数集合(Int Set)
- 压缩列表(Zip List)
所以,接下来的章节会先对这几种基楚数据结构进行学习。
1、基础数据结构
1.1 简单动态字符串(SDS:Simple Dynamic String)
1.1.1 介绍
下图(1-1)为SDS在Redis中的实现:
struct sdshdr
{
// 保存的字符串长度
int len;
// 未使用的长度
int free;
// 字节数组,用于保存字面量
char buf[];
};
可以看出,redis使用一个结构体来存储字符串,该结构体包含了以下部分:
- len 字段存储了字符串的长度
- free 字段记录了buf数组中的可用空间大小
- buf 为char类型数组,保存了实际的数据
下表(1-2)为sds在内存中的结构:
sdshdr | |||||||||||
len:5 | |||||||||||
free:4 | |||||||||||
buf:[ ] | -------> | R | e | d | i | s | \0 |
下表(1-3)表述了SDS实现和其特点之间的关系
sdshdr | int len | O(1)获取字符串长度 |
二进制安全 | ||
兼容部分C字符串函数 | ||
int free | 杜绝缓冲区溢出 | |
减少内存重新分配次数 |
1.1.2 特点
- O(1)获取字符串长度
这个很容易理解,众所周知,c语言使用'\0'来作为字符结尾,所以要获取字符串的长度需要遍历整个字符串,时间复杂度为O(n),但Redis通过SDS中的len属性记录字符串长度,所以时间复杂度为O(1)
-
杜绝缓冲区溢出
上文提到,C语言中对于字符串并不会记录其长度,其引发的问题除了上述以外,还有就是可能会造成缓冲区溢出。而Redis在每次对字符串进行修改之前,会先检测字符串的free属性值是否够用,若不够则会先进行空间扩展。
- 减少内存重新分配次数
在上文中有提到过空间扩展的概念,而Redis正是通过这样的方式来防止内存反复进行重新分配。
1. 空间预分配
在对字符串进行修改需要进行空间扩展时,Redis除了为SDS分配足够使用的空间外,还会额外分配一份未使用的空间。
a)当修改后的len属性小于1MB时,程序会分配同len一样的大小的未使用空间
b)当修改后的len属性大于1MB时,程序会分配同1MB大小的未使用空间
2. 空间惰性释放
当字符串的长度缩小时,Redis会将多余的空间记录在free属性中,等待将来使用。
- 二进制安全
上文提到c语言使用'\0'来作为字符结尾,这回导致字符串中不能出现'\0'字符串,这使c字符串得无法保存二进制文件,而Redis的SDS通过len属性来区分字符串结尾,则无此问题。
- 兼容部分C字符串函数
同样由于上述原因Redis的SDS可以使用部分的c语言字符串函数