Redis原理之String底层数据结构SDS(Simple Danamic String)

Redis是C语言开发的,但是Redis字符串没有直接使用C语言的字符串,学过c语言的朋友应该都知道,C语言字符串是以"\0"结尾的,那为什么Redis没有直接使用C语言的字符串,而是使用简单动态字符串SDS(Simple Danamic String)?

1、SDS(Simple Danamic String)数据结构

 struct sdshdr{
     //记录buf数组中已使用字节的数量
     //等于 SDS 保存字符串的长度
     int len;
     //记录 buf 数组中未使用字节的数量
     int free;
     //字节数组,用于保存字符串
     char buf[];
}

SDS保存字符串 “Redis”具体图示如下:

2、为什么这样设计?

解释: 

降低时间复杂度

    len 为字符串的实际长度,在Redis中获取字符串的key长度的时间复杂度为O(1) 通过 strlen key 命令可以获取 key 的字符串长度;而如果使用C语言的结构需要遍历整个字符串,时间复杂度为O(N),因为Redis是内存级别的,不应该反复遍历获取字符串长度。

②  杜绝缓冲区溢出

    free 为buf数组中剩余的空间大小,使用free杜绝了缓冲区溢出,会根据free和len空间的长度是否满足,如果不满足,会进行相应的空间扩展,然后在进行修改操作,所以不会出现缓冲区溢出

③ 减少修改字符串的内存重新分配次数

     C语言由于不记录字符串的长度,所以如果要修改字符串,必须要重新分配内存,因为如果没有重新分配。而对于SDS,由于len属性和free属性的存在,对于修改字符串SDS实现了空间预分配和惰性空间释放两种策略:

  1、空间预分配:对字符串进行空间扩展的时候,扩展的内存比实际需要的多,这样可以减少连续执行字符串增长操作所需的内存重分配次数。

  2、惰性空间释放:对字符串进行缩短操作时,程序不立即使用内存重新分配来回收缩短后多余的字节,而是使用 free 属性将这些字节的数量记录下来,等待后续使用。(当然SDS也提供了相应的API,当我们有需要时,也可以手动释放这些未使用的空间。)

④ 二进制安全

    二进制安全(Redis不是采用c语言字符串的以\0来判断字符串结束 由于这种限制,使得C字符串只能保存文本数据,像音视频、图片等二进制格式的数据是无法存储的, 而sds通过判断len的长度来判断字符串的长度

3、扩容规则

如果新字符串长度比SDS_MAX_PREALLOC小,则将其长度double,如果大于SDS_MAX_PREALLOC则再给SDS_MAX_PREALLOC空间。这个值redis定义的是1024*1024,即1MB。

这样一来,扩容一次多给一倍的存储空间,这样可以减少分配内存的次数,当然稍微有点浪费,代价是浪费一些内存,并且不会主动释放。

4 、String的编码格式

新版本数据结构

struct sdsshr<T>{
    T len;  //buf已使用长度       1byte
    T alloc;//alloc分配长度       1byte
    unsigned  flags;//sdshdr类型  1byte
    char buf[];//数组内容
}
  • int编码:8个字节的长整形,当数字长度小于20同时能够被强制转换成long, long类型使用int编码,长度大于20,或者超出long类型范围的时候会转换使用embstr
  • embstr编码:小于44字节的字符串(redis 3.2版本及以上)
  • raw编码:大于44个字节的字符串

对于embstrraw这两种encoding类型,其存储方式还不太一样。对于embstr类型,它将RedisObject对象头和SDS对象在内存中地址是连在一起的,但对于raw类型,二者在内存地址不是连续的。

对比两者内存布局可以发现:

 64 - 16(redisObject)- 1(len)- 1 (alloc) - flags = 45位;

45-1(\0)= 44位。

旧版本:

struct SDS {
    unsigned int capacity; // 4byte
    unsigned int len; // 4byte
    byte[] content; // 内联数组,长度为 capacity
}
这里的unsigned int 一个4字节,加起来是8字节.

内存分配器jemalloc分配的内存如果超出了64个字节就认为是一个大字符串,就会用到raw编码。

前面提到 SDS 结构体中的 content 的字符串是以字节\0结尾的字符串,之所以多出这样一个字节,是为了便于直接使用 glibc 的字符串处理函数,以及为了便于字符串的调试打印输出。所以我们还要减去1字节
64byte - 16byte - 8byte - 1byte = 39byte
 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值