起因

今天面试惨败,面试官问了不到10个问题就让我出来写题了……对其中的一个题目印象深刻:

Redis获取字符串长度的复杂度是多少?

刚开始我是一脸懵逼的,因为不清楚Redis的字符串类型是怎么实现的,所以完全没法答下去了……回来后马上开始学习。

字符串类型

字符串是Redis里非常常见的类型,而用C实现的RedisJava不一样。在C里字符串是用长度为N+1的字符数组实现的,且使用空字符串'\0'作为结束符号。获取字符串的长度需要遍历一遍,找到空字符串'\0'才知道字符串的长度,复杂度是O(N)

如果有一个长度非常大的字符串,单线程的Redis获取它的长度就可能会阻塞很久,这是不能接受的,所以Redis需要一种更高效的字符串类型。

SDS

Redis实现了一个叫SDS(simple dynamic string)的字符串类型,其中有两个变量来分别代表字符串的长度和字符数组未使用的字符数量,这样就可以用O(1)的复杂度来获取字符串的长度了,而且同样也是使用空字符串'\0'作为结束符号。

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

现在已经可以回答上面的面试题了,其实是非常简单的一个问题,怪不得答不出来面试官马上就说面试结束了……

扩容机制

SDS在字符数组空间不足于容纳新字符串的时候会自动扩容。

如果把一个C字符串拼接到一个SDS后面,当字符数组空间不足时,SDS会先扩容到刚好可以容纳新字符串的长度,然后再扩充新字符串的空字符长度,最终SDS的字符数组长度等于 2 * 新字符串 + 1(结束符号'\0')。不过当新字符串的大小超过1MB后,扩充的空字符长度大小会固定为1MB

之所以会有这个机制,是因为Redis作为一个NoSQL数据库,会频繁的修改字符串,扩容机制相当于给SDS做了一个缓冲池。把SDS连续增长N次字符串需要内存重分配N次优化成了SDS连续增长N次字符串最多需要内存重分配N次,这其实和Java里的StringBuilder实现思想是一样的。

后记

这次翻车是有原因的,我看过两本关于Redis的书,里面都是讲Redis如何实战的但是并没有讲Redis的设计和实现。这也就导致了面试很尴尬,因为面试官最喜欢问原理相关的东西了,所以以后学习技术的时候不要从实战类的书籍开始了,还是先看懂原理比较好。

参考资料

这是《Redis设计与实现》里字符串一节的总结。


Yuicon
495 声望24 粉丝