目录
使用过 Redis 的都知道 Redis 用的最多的可能是它的 Key/Value 的缓存,在 Redis 用作 Key/Value 的缓存时,Value 有若干种数据类型,分别是 String、List、Set、Sorted Set 和 Hash。不同的 Value 类型对应了不同的数据结构,我们分别来了解一下 Redis 各种 Value 类型的数据结构。
这次先来了解一下,Redis 的 String 类型所对应的数据结构。我这里要首先感谢《Redis 设计与实现》一书的作者 黄健宏 先生,他写出如此优秀的书籍从而让我们能够学习高性能的 Redis 的内部实现原理。
Redis 是使用 C 语言实现的,C 语言本身就是一种运行时非常高效的程序设计语言。但是 Redis 的字符串结构并没有沿用 C 语言常规的存储字符串的方式。在 C 语言中,字符串是以数组的形式存储在内存中的,并且以 \0 作为字符串的结尾。在 C 语言中,读取字符串、计算字符串长度、连接字符串、拷贝字符串等函数,都是以 \0 来进行判断,而如果在写代码时对缓冲区检查不严格,则会导致缓冲区溢出。缓冲区溢出这种安全问题也是 C 语言一个很严重的问题。C 语言设计的初衷是希望把尽可能多的控制权交给程序员来保证 C 语言的灵活和自由,但是灵活和自由的同时也付出了很多惨痛的代价。
当然了,上面的存储方式是 C 语言的方式,其他的语言就未必了,如果使用过 Delphi 的话,就知道 Delphi 的字符串就不是以 \0 作为结束的,而是在字符串的开头位置放入了字符串长度一个标识。
现在,我们来看看 Redis 的实现,我这里是 Redis 2.6 的实现源码(版本低一些,阅读时会相对容易一些)。
一、SDS 结构体
在 Redis 中的字符串是结合了 C 语言字符串特性(即以 \0 结尾)和一种被称为 SDS 的数据结构共存的字符串结构,其结构的定义在 sds.h 的头文件中,该数据结构的定义如下(所有的中文注释都是我注释的,官方源码中是不可能提供中文注释的,如果注释有误希望可以指出):
struct sdshdr {
/* 已使用缓冲区的长度 */
int len;
/* 未使用缓冲区的长度 */
int free;
/* 缓冲区 */
char buf[];
};
该结构体中包含三个成员,分别是 len、free 和 buf,其中 len 用来保存已经使用的缓冲区的长度,free 用来保存未使用的缓冲区的长度,buf 是真正的缓冲区的字符数组。buf 中存储的字符串仍然是以 \0 结尾,但是它的长度并不计算在 len 中,这一点和 C 语言是相同的,使用 strlen 计算字符串长度的时候,也没有把 \0 计算在内。
确定了数据结构以后,所有的操作就是围绕数据结构来展开了,就好比我们写代码,确定了表结构,生成了实体类,然后业务处理就是围绕输入和针对实体类的具体操作了。
二、获取字符串的长度
在 C 语言中,求字符串的长度是逐个字符进行遍历统计,直到遇到 \0 就是字符串的结束,这样就能得到字符串的长度了。也就是说,在 C 语言中获取字符串长度的时间复杂度为 O(n)。
在 SDS 结构体中,获取字符串的长度的时间复杂度则为 O(1),因为在 SDS 结构体中已经有一个成员来保存已使用缓冲区的长度了,它就是实际意义上的字符串的长度,因此在 SDS 字符串长度的时