目录
Redis 没有直接使用 C 语言传统的字符串表示(以空字符结尾的字符数组), 而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型, 并将 SDS 用作 Redis 的默认字符串表示
-
SDS结构
struct sdshdr{
// 1bytes 数组容量
int8 capacity;
//1bytes 数组长度
int8 len;
//1bytes 特殊标志位
int8 flags;
//长度取决于实际情况 数组内容
byte[] content;
};
content除了保存字符串的字符外, 还会在末尾保存一个空字符 '\0' , 空字符不计算在 len 属性之中,遵循空字符结尾的好处是可以重用一部分C字符串的函数
-
redis字符串存储两种形式(embstr和raw)
我们先看下在客户端运行的代码:
127.0.0.1:6379> set test_str 11111111111111111111111111111111111111111111
OK
127.0.0.1:6379> debug object test_str
Value at:0x7f7fb8215100 refcount:1 encoding:embstr serializedlength:12 lru:11329907 lru_seconds_idle:16
127.0.0.1:6379> set test_str 111111111111111111111111111111111111111111111
OK
127.0.0.1:6379> debug object test_str
Value at:0x7f7fb826dc40 refcount:1 encoding:raw serializedlength:12 lru:11329942 lru_seconds_idle:4
127.0.0.1:6379>
从代码可以发现当字符串长度为44的时候,是以embstr形式存储,大于44的时候则以raw的形式存储,那么为什么以44为分界线呢?
我们简单看下redis对象头部
struct redisObject {
// 类型 4bits
int4 type;
// 编码 4bits
int4 encoding;
// lru 24bits
int24 lru;
// 引用计数 4bytes
int32 refcount;
// 8bttes (64位操作系统) 指向底层实现数据结构的指针
void *ptr;
} robj;
embstr使用malloc(动态内存分配)分配一次内存,对象头和SDS对象连续存储一起,而raw需要两次,两个对象头不是连续存储在一起。在内存分配器(jemalloc)中,分配内存大小的单位是2/4/8/16/32/64字节等,所以可以看出最长的就是用64字节,64字节减去redis头部(16)和SDS对象(3)还剩下45个,字符串结尾又是以NULL结尾,所以字符串最大长度就是44。
embstr(embedded string):字符串的空间将会和redisObject对象的空间一起分配,两者在同一个内存块中。采用这个方式可以减少内存分配的次数,提高内存分配的效率,降低内存碎片率。
raw:调用两次内存分配函数来分别创建 redisObject
结构和 sdshdr
结构, 而 embstr
编码则通过调用一次内存分配函数来分配一块连续的空间, 空间中依次包含 redisObject
和 sdshdr
两个结构
注意:value是整型且长度满足long长度,存储类型为int,可以结合我上面操作试试
SDS和C字符串区别
- 获取字符串长度的时间复杂度:SDS:O(1) ,C:O(n)
- 二进制安全:C 字符串中的字符必须符合某种编码(比如 ASCII),SDS 都会以处理二进制的方式来处理 SDS 存放在
content
数组里的数据(这里要注意写代码时候有时候要注意转码格式,要和redis一致,防止乱码) - 惰性空间释放: 当 SDS API 需要缩短 SDS 保存的字符串时, 程序并不立即使用内存重分配来回收缩短后多出来的字节
- 空间预分配:当字符串小于1MB时候,扩容采用加倍方式,大于1MB则每次给他分配1MB
本文来自于redis深度历险,redis设计与实现,redis官方文档等待总结出来的,有什么不足请谅解,最后还是大家有空去去读下钱文品的redis深度历险这本书很给力的。