本文转载自微信公众号「 编程珠玑」,什不使用转载本文请联系 编程珠玑公众号。直接C字字符 作者:守望,符串linux应用开发者,而自目前在公众号【编程珠玑】 分享Linux/C/C++/数据结构与算法/工具等原创技术文章和学习资源。定义动态 Redis (一个使用ANSI C编写的简单开源、支持网络、什不使用基于内存、直接C字字符可选持久性的符串键值对存储数据库。)没有直接使用 C 语言传统的而自字符串表示redis中的字符串,而是定义动态使用了一种名为简单动态字符串(simple dynamic string,SDS)的简单抽象类型, 并将 SDS 用作 Redis 的什不使用默认字符串。 那么,直接C字字符为什么要用这种数据结构替代传统的符串字符串呢?我们先回顾一下C字符串。 C语言传统字符串 C语言传统字符串是以空字符结尾的字符数组。例如: 计算字符串的长度: C语言传统字符串我们应该已经很熟悉了,这里就不再继续介绍了。 更多相关内容参考《sizeof,strlen,数组,字符串整在一起的那些坑》和《C语言入坑指南-数组之谜》。 简单动态字符串 redis中的简单动态字符串定义如下: 它有很多种,这里选择了长度为8字节范围的进行介绍。看起来很简单对不对? __attribute__ ((packed)) 取消了默认的字节对齐,使得flags前后不会有潜在的填充字段,也便于网络传输(扩展内容参考《理一理字节对齐的那些事》)。len表示buf中存储了的内容的长度;alloc表示已经分配的空间。 那么,定义成这样的SDS有什么好处呢? 常数复杂度获取长度 我们都知道,strlen获取C传统字符串长度的时间复杂度为O(N),而上面的结构中,获取字符串长度的时间复杂度为常数,因为len字段存储了字符串的长度,这样的做法虽然多占用了一点空间,换来的却是效率的提升。 实际上这种做法,在很多地方都很常见,站群服务器例如C++中的标准容器,如vector获取其大小,string获取其长度。 预分配空间减少内存分配次数 实际上,在创建新的sds的时候,它并不仅仅申请要使用的内存,而是额外申请了一些空间,以避免下次修改的时候又需要重新申请内存。 什么意思呢? 比如说,你有一个字符数组: 现在你想存储helloworld,怎么办?原先的空间已经确定了,没有办法存储这么多字符串,你只能重新申请空间,然后还要把原先的hello拷贝到新申请的空间中去。如果有频繁地修改字符串,就会导致系统中频繁的内存申请,释放,拷贝,这样还能有高效的redis吗? 因此在redis中,如果有这样的情况,分配新的高防服务器空间的时候,会预分配一些空间,以备下次使用。 惰性释放空间 而正因如此,出现字符串缩短的时候,也没有必要直接释放内存,只需要更新字符串,记录当前使用的长度即可,你说,下次字符串又增长的时候,不就又用上了吗? 保存二进制数据 看下面的字符串: 你说下面的字符串,strle长度是多少?不是10,也不是11,而是5。为啥?遇到\0就计算结束了呗。所以要想存储一些特殊的字符串,即中间可能出现\0的字符串,传统的C字符串还不好办呢。 sds就不一样了,管你存什么,反正我长度是记录在len字段中了,输入写入多少,我记录多少。因此它可以保存二进制数据。 扩展可以参考《NULL,0,\0,“0”,"\0"你真的分得清吗?》 兼容传统字符串的常见用法 虽然redis新定义了sds这样的结构,但是能应用于传统C字符串的函数,同样可以应用于sds。这点在《数组下标-1你见过吗?》中已经简单提到过了。 所以,类似下面这样的操作,也是安全的: 所以你现在明白为什么要指向buf了吧?适用于传统C字符串的函数,也能用在sds上。 而正因如此,我们看到源码中,有很多地方sds使用了下标-1访问一些内容: 例如sdsIncrLen函数中 s[-1],等价于 *(s-1),下面就很容易理解了: 所以下次看到下标为负,可不要觉得奇怪了。 总结 实际上当你了解C++的vector的时候,你会发现,它们利用的思想是惊人的相似 本文旨在通过了解redis中sds的实现,学习其中的设计思想和策略。